目录
pytest框架的优势
- 简单灵活,容易上手;支持参数化; 测试用例的skip和xfail处理;
- 能够支持简单的单元测试和复杂的功能测试,还可以做selenium/appnium等自动化测试、接口自动化测试(pytest+requests);
- pytest具有很多第三方插件,并且可以自定义扩展, 较好的如pytest-allure(完美html测试报告生成)、pytest-xdist(多 CPU分发)等;
- 可以很好的和jenkins集成;
pytest框架的规范
- 首先要import pytest
- 与Unittest使用方法相同的setUp和tearDown
- 在命令行中,可以使用如下命令:
pytest --v(最高级别信息)
pytest -v -s 文件名(带控制台输出结果) - 测试文件名称以 test_ 开头(以 _test 结尾也可以)
- 测试类名称以 Test 开头,并且不能带有 init 方法
- 测试方法名称以 test_ 开头
测试文件的运行
import pytest
pycharm运行
在main下
pytest.main([’-s’,‘文件名’])
控制台运行
pytest -s 文件名
前端自动化应用
登录
- 场景:测试用例执行时,有些需要登录执行,有些不需要
- 在登录函数前加@pytest.fixture(),需要登录的在测试方法中传入登录函数,范围默认函数级别
@pytest.fixture()
def login():
print('login')
def test_l(login):
print('case1:l')
def test_nl():
print('case2:nl')
if __name__=='__main__':
pytest.main(['-s','test_fitt.py'])
conftest
yield
- 场景:测试方法后销毁清除数据,范围是模块级别,类似setupClass
- 在同一模块中加入yield关键字,第一次调用返回结果,第二次执行yield后面的语句
- 在登录方法中加yield,之后加销毁清除的步骤
- 希望返回值使用addfinalizer
fixture的自动应用
- 场景:不想原测试方法有任何改动,或全部都自动实现自动应用,没有特例,也不需要返回值
- @pytest.fixture(autouse=true)
- @pytest.mark.usefixtures(‘自动实现的函数’)
@pytest.fixture(scope='module',autouse=True)#一整段都自动应用
def open_browser():
print('dakai ')
yield
print('teardown')
print('guanbi')
@pytest.fixture()
def login():
print('login')
def test_l(login):
print('case1:l')
def test_nl():
print('case2:nl')
if __name__ == '__main__':
pytest.main(['-s', 'test_fitt.py'])
@pytest.fixture()
def open_browser():
print('dakai ')
yield
print('teardown')
print('guanbi')
@pytest.mark.usefixtures('open_browser')#加了此句的自动应用
def test_login():
print('login')
def test_l():
print('case1:l')
@pytest.mark.usefixtures('open_browser')
def test_nl():
print('case2:nl')
if __name__ == '__main__':
pytest.main(['-s', 'test_fitt.py'])
带参数传递
- @pytest.fixture(params=[1,2,3,‘linda’])
- 方法参数中写request
test_user_data = [12,3,'linda']
@pytest.fixture(params=test_user_data)
def login_r(request):
usr = request.param
print('登录:',usr)
return usr
def test_2(login_r):
print(login_r)
if __name__ == '__main__':
pytest.main(['-s', 'test_fitt.py'])
mark参数化
- 场景:测试数据和测试预期结果一一对应
- @pytest.mark.parametrize
@pytest.mark.parametrize('test_input,expected',[
('3+6',8),('1+5',6)
])
def test_eval(test_input,expected):
assert eval(test_input)==expected
if __name__ == '__main__':
pytest.main(['-s', 'test_fitt.py'])
mark参数化和数据驱动
@pytest.mark.parametrize(‘需要的函数’,需要的数据,indirect = True)
test_user_data = [12,3,'linda']
@pytest.fixture(scope='module')
def login_r(request):
usr = request.param
print('登录:',usr)
return usr
@pytest.mark.parametrize('login_r',test_user_data,indirect=True)
def test_2(login_r):
a = login_r
print('login_r',a)
assert a!=''
if __name__ == '__main__':
pytest.main(['-s', 'test_fitt.py'])
多个参数化和数据驱动结合
- 场景:数据读入,多个数据组合进行测试
- 通过字典格式传入数据
单个数据输入
test_user_data = [{'username':'111','password':'777777'},
{'username':'222','password':'888888'},
{'username':'123','password':''}]
@pytest.fixture(scope='module')
def login_r(request):
usr = request.param['username']
psw = request.param['password']
print('登录:',usr)
if psw:
return True
else:
return False
@pytest.mark.parametrize('login_r',test_user_data,indirect=True)
def test_2(login_r):
a = login_r
print('login_r',a)
assert a,'none'
if __name__ == '__main__':
pytest.main(['-s', 'test_fitt.py'])
组合数据输入
test_user_data1 = [{'username':'111','password':'777777'},
{'username':'222','password':'888888'},
{'username':'123','password':''}]
test_user_data2 = [{'q':'tb','count':2,'page':3},
{'q':'ali','count':2,'page':3},
{'q':'pdd','count':2,'page':3}
]
@pytest.fixture(scope='module')
def login_r(request):
usr = request.param['username']
psw = request.param['password']
print('登录:',usr)
if psw:
return True
else:
return False
@pytest.fixture(scope='module')
def query(request):
q = request.param['q']
count = request.param['count']
page = request.param['page']
print('ci:',q)
return request.param
#从下往上执行,先执行离函数名进的
@pytest.mark.parametrize('query',test_user_data2,indirect=True)
@pytest.mark.parametrize('login_r',test_user_data1,indirect = True)
def test_2(login_r,query):
a = login_r
print('login_r',a)
print(query)
assert a,'none'
if __name__ == '__main__':
pytest.main(['-s', 'test_fitt.py'])
mark中的skip
- 场景:不想运行的用例、无法运行的用例、需要跳过的用例
- @pytest.mark.skip跳过,skipif在某些条件下才希望通过
@pytest.mark.skipif(sys.platform =='win32',reason ='不在windows下运行')
def test_login():
print('tiaoguo')
mark中的xfail
- 场景:测试通过是预计会失败,希望由于某些情况而失败
- @pytest.mark.xfail
@pytest.mark.xfail
def test_xfail():
print(broken_fixture())
def broken_fixture():
raise Exception('中断')
自定义标记mark只执行部分用例
- 场景:多平台代码在一个文件里
- @pytest.mark.apptest
- 执行 -s 文件名 -m 标记
def test_q1():
pass
@pytest.mark.webtest
def test_q2():
pass
@pytest.mark.ios
def test_q2():
pass
@pytest.mark.android
def test_q2():
pass
pytest -s test_fitt.py -m webtest
通过文件名类名方法及组合调用部分用例
- 场景:只执行符合要求的部分用例
- 方法1:直接输入文件名
pytest -v test_a1.py::TestClass::test_one
- 方法2:使用 -k
pytest -k "TestClass or test_one"
pytest -k "TestClass and test_one"
执行用例出错时停止
- 遇到错误停止测试 -x
- 用例错误个数达到指定数量n时停止测试 --maxfail =n
pytest -x -v -s test_fitt.py
pytest -x -v -s test_fitt.py --maxfail = 10
超时,失败重新执行
- 场景:超时导致失败
- 使用pytest-rerunfailures插件(使用pip工具下载)
- 具体操作:在命令行输入pytest --reruns [次数] [文件名]
多条断言
- 场景:多个数据比较,防止出现一个assert不通过,后面的就都不通过的情况
- 使用:pytest.assume (使用pip install pytest-assume 命令下载assume插件)
def test_multiple_assert():
pytest.assume(1==2)
pytest.assume(2==2)
pytest.assume(3==2)
用例依赖
- 在web测试中,上下测试用例页面切换有依赖关系;
- 在修改信息的页面中,依赖于前用例已经创建好的信息,如修改账号信息,依赖于已经创建好的数据
- 使用pytest-ordering插件
- 具体操作:@pytest.mark.run(order = 顺序)
import time
value = 0
@pytest.mark.run(order = 2)
def test_add2():
print('2')
time.sleep(2)
assert value ==10
@pytest.mark.run(order = 1)
def test_add():
print('1')
global value
value =10
assert value ==10
设定好顺序后会先执行order=1的方法,再执行order=2的方法
多线程并行和分布式执行
- 场景:多机器一起测试减少时间
- pytest-xdist 插件
- 前提:用例之间是独立的,没有先后顺序
- 多cpu并行执行, pytest -n 并行数量
- 多终端下一起执行
pytest-html生成报告
- pytest-html
pytest -v -s --html = report.html --self-contained-html