文章目录
一、Pytest简介
测试框架是用来组织测试用例并进行运行控制的。使用框架可以做到:
- 挑选用例并批量执行
- 单条用例失败后不影响其他用例执行
- 不同范围的测试准备和环境清理方法
- 一套断言机制
- 运行完生成测试报告
二、用例编写和断言
- 函数式写法
# test_demo1.py
import requests
def test_1():
assert 1+1 >= 2
def test_2():
res = requests.get('https:/xxxxxx.com')
assert res.status_code == 200
*注意:测试脚本,测试函数名都要以test开头
- 类式写法
import requests
class TestDem2(object):
base_url = 'http://add/'
def test_add1():
res = requests.get(self.url, params = {'a':1,'b':2})
assert res.text == 3
def test_add2():
res = requests.get(self.url, params = {'a':-1,'b':2})
assert res.text == 1
注意:
- 测试脚本,测试函数名都要以test开头, 类名以Test开头
- 测试类不需要继承unittest.TestCase, 测试类中不能有__init__方法
- 断言相等
assert 1+1 == 2
assert 1+2 != 2
assert 5 >= 4
assert {'a': 1, 'b': 2} == {'b':2, 'a':1} # 也可用例判断列表和字典相等
- 断言包含
assert '百度' in driver.title
assert 1 in [1,2,3]
- 断言True/False/None
assert 3>2 is True
assert 3>2 is not False
assert [] is not None
注意:1.is和==的不同,为值相等,is必须内存地址相同(是同一个对象)1True
但 1 is not True。
2.Python中[],{},(),0,‘0’,’'都被视为假,因此可以用assert list1来判断list1不为空。
用例运行
- 命令行运行
打开命令行,进入脚本所在目录,执行pytest test_demo1.py
注:如果不在脚本所在目录需加上脚本路径,如:pytest D:/test_demo1.py - 在脚本中调用执行
if __name__ == '__main__':
pytest.main(["test_demo1.py"])
- 常用执行参数
1.-v: 详细显示模式(否则执行通过的用例只显示一个点)
2.-q: 安静模式,不显示环境信息
3.-s: 命令行显示print输出而不是捕获并输出到报告中
测试准备与环境清理
- 一般来说,每条用例都需要一定的预置条件或数据准备。而如果用例执行了修改操作或产生了新的数据,还应该对环境进行还原和清理。在测试框架中,这种准备方法叫做setup,清理方法叫做teardown,这两种方法统称为Test Fixtures(夹具),像一个夹子一样把用例夹在中间。
- 这种Test Fixtures是有一定范围的。可以每次只夹一条用例(相当于每条用例都先执行setup方法,执行完用例再执行teardown方法),也在一批用例执行前执行一次setup,都执行完再执行teardown。
- Pytest中的Test Fixtures方法有5种范围:
1.Session会话级:运行一次Pytest算一次会话。运行期间只setup/teardown一次
2.Package包级:对每个包Python包setup/teardown一次
3.Module模块级:对每个Python脚本setup/teardwon一次
4.Class级:对每个测试类setup/teardown一次
5.Function级/Method级:对每个测试函数、测试方法setup/teardown一次
内置的模块/类/函数/方法级别的Fixtures
- setup_module()/teardown_module()
- setup_class()/teardown_class()
- setup_function()/teardown_function()
- setup_method()/teardown_method()
session > module > class > function执行顺序
自定义Fixtures方法
用于以下场景
- 自定义级别的测试准备和环境清理方法,如会话级,包级
- 全局变量,如浏览器driver,数据库连接,用例数据等
- 测试辅助步骤,如获取token,登录步骤等
import pytest
@pytest.fixture
def my():
print("setup ...")
yield 123
print('teardown')
@pytest.fixture(scope='session', autouse=True)
def my2():
print('my2 setup ...')
注:如果不需要清理方法,yield也可以使用return返回,如果setup出错,用例及teardown则不会执行。
自定义Fixtures方法的使用
Fixtures方法可以通过注入方式注入到用例中使用,Fixture方法之间也可以相互注入。Fixtures方的主要使用方式有3种:
- Fixture方法名直接作为用例参数使用(可以拿到fixture方法返回值)
- 使用@pytest.userfixtures()
- 自动使用,fixture方法设置了autouse=True
import pytest
@pytest.mark.usefixtures('my')
def test_a():
print('test_a')
def test_b(my): # fixture方面名直接作为参数使用
print("test_b")
print(my)
def test_c():
print("test_c")
输出:
collected 3 items
test_aaa.py::test_a my2 setup ...
setup ...
test_a
PASSEDteardown
test_aaa.py::test_b setup ...
test_b
123
PASSEDteardown
test_aaa.py::test_c test_c
PASSED
Fixtures方法共享、范围及优先级
Fixtures方法一般作为公用的辅助方法或全局变量来使用,因此需要在不同用例中都能使用。一般我们用conftest.py这个文件来集中管理Fixtures方法。
生效范围:对conftest.py所在目录及子目录下的所有用例生效。
优先级:用例就近原则。用例文件中的Fixtures方法>当前目录conftest.py中的Fixtures方法>上级目录conftest.py中的Fixtures方法>…
因此,在项目不同模块中使用conftest.py可以实现不同级别的测试准备、清理或辅助方法,如:
数据驱动
在测试用例中,数据的丰富性是非常重要的。同一个测试流程往往需要测试多组数据。我们并不需要把用例复制很多遍然后改为不同的数据。只需要使用数据驱动即可。首先我们需要一个模板,如测试加法接口:
import requests
def test_add():
res = requests.get('/add/?a=1&b=2')
assert res.text == '3'
假如我们要验证1+2=3,-1+2=-1,1.5+2.5=4这三组数据。
- 我们将这三个数据参数化成a,b,sum,通过参数传入用例,如下:
def test_add(a,b,sum): # 需要传入a,b,sum三个参数
res = requests.get(f'add/?a={a}&b={b}')
assert res.text == sum
- 我们把每一组放到一个列表中作为一组数据,那么这三组数据为:
data = [[1, 2, 3], [-1, 2, -1] ,[1.5, 2.5, 4]]
- 使用@pytest.mark.parametrize(‘a, b, sum’, data) 将数据分批注入用例,我们需要将data中的三个参数映射给用例参数a,b,sum,因此parametrize中的’a,b,sum’要与用例参数一致。和子列表中的数据个数一致。
完整代码如下:
import requests
import pytest
data = [[1, 2, 3], [-1, 2, 1]
@pytest.mark.parametrize('a, b, sum', data)
def test_add(a,b,sum): # 需要传入a,b,sum三个参数
res = requests.get(f'/add/?a={a}&b={b}')
assert res.text == str(sum)
输出:
plugins: base-url-1.4.1, check-0.3.5, html-1.22.0, metadata-1.8.0
collected 2 items
test_aaa.py::test_add[1-2-3] PASSED
test_aaa.py::test_add[-1-2-1] PASSED