0.pytest 常用插件
- pytest-html
- pytest-xdist
- pytest-ordering
- pytest-rerunfailures
- allure-pytest
- pytest-base-url
- pytest
1.pytest 常用命令参数
- -h: 查看所有可用参数
- -s:输出调试信息,包括 print 打印的信息。
- -v:显示更详细的信息。
- -k=value:用例的 nodeid 包含 value 值则用例被执行。
- -n=num:启用多线程或分布式运行测试用例。需要安装 pytest-xdist 插件模块。
- -m = 标签名:执行被 @pytest.mark. 标签名 标记的用例。
- -x:只要有一个用例执行失败就停止当前线程的测试执行。
- –maxfail=num:与 - x 功能一样,只是用例失败次数可自定义。
- –reruns=num:失败用例重跑 num 次。需要安装 pytest-rerunfailures 插件模块。
2.pytest 默认的测试用例规则
一般在项目当中新建一个用例包
注意点:
- 包名和模块名以及用例名(函数,方法)必须符合以 test 开头或者 test 结尾
- 测试用例类必须以 Test 开头,而且这个测试类不能有 init 方法
- 以上规则属于 pytest 默认的形式,如果需要修改可以配置 pytest.ini 进行改变
测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def test_login_01(): print("1.打开页面") print("2.输入账号密码") print("3.点击登录按钮")
class TestLogin_01: def test_login_02(self): print("实例方法1:点击登录页面") print("实例方法2.输入账号密码") print("实例方法3.点击登录按钮")
@classmethod def test_login_03(cls): print("类方法1.点击登录页面") print("类方法2.输入账号密码") print("类方法3.点击登录按钮")
|
3.pytest 用例执行的两种方式
1. 使用命令 pytest -vs
2. 使用主函数
1 2 3 4 5 6 7
| import pytest
if __name__ == '__main__': pytest.main([])
|
4.pytest 标记跳过用例
跳过用例的方式有 2 中类型:
无条件跳过
1 2 3 4 5 6
| @pytest.mark.skipif(reason='无条件跳过该测试用例') def test_login_01(): print("1.打开页面") print("2.输入账号密码") print("3.点击登录按钮")
|
有条件跳过
满足条件就跳过不执行
1 2 3 4 5
| @pytest.mark.skipif(3>2, reason='如果前面条件为真,则跳过该用例,不执行') def test_login_04(): print("测试函数 4:打开页面") print("测试函数 4:输入账号密码") print("测试函数 4:点击登录按钮")
|
不满足条件,就不跳过,正常执行
1 2 3 4 5
| @pytest.mark.skipif(3<2, reason='如果前面条件为假, 则不跳过用例正常执行') def test_login_05(): print("测试函数 5:打开页面") print("测试函数 5:输入账号密码") print("测试函数 5:点击登录按钮")
|
5.pytest 控制测试用例的执行顺序
默认是按照文件名以及用例名的先后顺序进行执行的,想要改变默认执行规则,可以进行修改调整。
- pytest-ordring ??? 这里也没有用这个包的内容呢??
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @pytest.mark.run(order=3) def test_login_06(): print("测试函数 6:打开页面") print("测试函数 6:输入账号密码") print("测试函数 6:点击登录按钮")
@pytest.mark.run(order=2) def test_login_07(): print("测试函数 7:打开页面") print("测试函数 7:输入账号密码") print("测试函数 7:点击登录按钮")
@pytest.mark.run(order=1) def test_login_08(): print("测试函数 8:打开页面") print("测试函数 8:输入账号密码") print("测试函数 8:点击登录按钮")
|
6.pytest 标记失败的用例
标记预期会出现异常或者失败的测试用例,只有出现异常才符合预期,如果不出现异常反而不符合预期。
正常预期失败
1 2 3 4 5 6
| @pytest.mark.xfail(reason='出现了被除数为 0 的情况') def test_login_09(): print("测试函数 9:打开页面") print("测试函数 9:输入账号密码") print(1/0) print("测试函数 9:点击登录按钮")
|
意外预期通过
1 2 3 4 5 6
| @pytest.mark.xfail(reason='意外通过了被除数为 0 的情况') def test_login_10(): print("测试函数 10:打开页面") print("测试函数 10:输入账号密码") print("测试函数 10:点击登录按钮")
|
执行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
testcases/test_01.py::test_login_09 测试函数 9:打开页面 测试函数 9:输入账号密码 XFAIL (出现了被除数为 0 的情况) testcases/test_01.py::test_login_10 测试函数 10:打开页面 测试函数 10:输入账号密码 测试函数 10:点击登录按钮 XPASS (意外通过了被除数为 0 的情况)
============================================== 6 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.06s ============================================== (.venv) ➜ StudyPytest
|
7.pytest 标记参数化
参数化的使用场景主要是针对同样的用例执行流程,输入不同的数据,结合参数进行数据驱动的测试。
1 2 3 4 5 6 7 8
| @pytest.mark.parametrize(['username', 'password'], [('13100000001', 'pass01'), ('13100000002', 'pass02'), ('13100000003', 'pass03')]) def test_login_11(username, password): print("测试函数 11:打开页面") print("测试函数 11:输入账号", username) print("测试函数 11:输入密码", password) print("测试函数 11:点击登录按钮")
|
一个用例可以输入不同组的参数进行数据驱动测试,有多少个参数,用例就执行多少遍
从 excel 中提取参数化数据进行测试
只要返回的参数是
从 csv 中提取参数化数据进行测试
8. pytest 的 setup/teardown 相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import pytest
def setup_module(module): print("setup_module")
def teardown_module(module): print("teardown_module")
class Test02:
@classmethod def setup_class(cls): print("setup_class")
@classmethod def teardown_class(cls): print("teardown_class")
def setup_method(self, method): print("setup_method")
def teardown_method(self, method): print("teardown_method")
def test_02_01(self): assert 3 > 2, '3确实大于2'
def test_02_02(self): assert 3 < 2, '失败'
def test_02_03(self): assert 1 / 0 == 0, '报错了'
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| (.venv) ➜ StudyPytest pytest -vs testcases/test_02.py::Test02 ============================================================= test session starts ============================================================== platform darwin -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0 -- /Users/candy/Project/PycharmProjects/StudyPytest/.venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.12.4', 'Platform': 'macOS-13.6.7-x86_64-i386-64bit', 'Packages': {'pytest': '8.3.2', 'pluggy': '1.5.0'}, 'Plugins': {'html': '4.1.1', 'metadata': '3.1.1', 'rerunfailures': '14.0', 'allure-pytest': '2.13.5', 'ordering': '0.6', 'base-url': '2.1.0', 'xdist': '3.6.1'}, 'Base URL': ''} rootdir: /Users/candy/Project/PycharmProjects/StudyPytest plugins: html-4.1.1, metadata-3.1.1, rerunfailures-14.0, allure-pytest-2.13.5, ordering-0.6, base-url-2.1.0, xdist-3.6.1 collected 3 items
testcases/test_02.py::Test02::test_02_01 setup_module setup_class setup_method PASSED teardown_method
testcases/test_02.py::Test02::test_02_02 setup_method FAILED teardown_method
testcases/test_02.py::Test02::test_02_03 setup_method FAILEDteardown_method teardown_class teardown_module
|
9.pytest 前后置 fixture 固件
当用例执行之前或者执行完成后需要固定的测试环境可以卸载 fixture 当中,然后进行使用。
(这个相对于之前的 setup,teardown 就要清爽多了啊)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| from _pytest.fixtures import fixture
@fixture(scope='function') def e_x_function(): print('function级别开始') yield print('function级别结束')
@fixture(scope='class') def e_x_class(): print('class级别开始') yield print('class级别结束')
@fixture(scope='module') def e_x_module(): print('module级别开始') yield print('module级别结束')
@fixture(scope='session') def e_x_session(): print('session级别开始') yield print('session级别结束')
@fixture def e_x_fixture(): print('不写范围function级别开始') yield print('不写范围function级别结束')
|
测试用例中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Test03: def test_03_01(self, e_x_function): assert 3 > 2, '为真,通过0301'
def test_03_02(self, e_x_class, e_x_function): assert 3 < 2, '为假,不通过0302'
def test_03_03(self, e_x_class, e_x_module, e_x_function): assert True, '为真,通过0303'
def test_03_04(e_x_module, e_x_function): assert True, '为真,通过 0304' def test_03_05(e_x_fixture): assert True, '为真,通过 0304'
|
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| (.venv) ➜ StudyPytest pytest -vs -k=test_03 ============================================================= test session starts ============================================================== platform darwin -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0 -- /Users/candy/Project/PycharmProjects/StudyPytest/.venv/bin/python cachedir: .pytest_cache metadata: {'Python': '3.12.4', 'Platform': 'macOS-13.6.7-x86_64-i386-64bit', 'Packages': {'pytest': '8.3.2', 'pluggy': '1.5.0'}, 'Plugins': {'html': '4.1.1', 'metadata': '3.1.1', 'rerunfailures': '14.0', 'allure-pytest': '2.13.5', 'ordering': '0.6', 'base-url': '2.1.0', 'xdist': '3.6.1'}, 'Base URL': ''} rootdir: /Users/candy/Project/PycharmProjects/StudyPytest plugins: html-4.1.1, metadata-3.1.1, rerunfailures-14.0, allure-pytest-2.13.5, ordering-0.6, base-url-2.1.0, xdist-3.6.1 collected 21 items / 17 deselected / 4 selected
testcases/test_03.py::Test03::test_03_01 function级别开始 PASSED function级别结束
testcases/test_03.py::Test03::test_03_02 class级别开始 function级别开始 FAILED function级别结束
testcases/test_03.py::Test03::test_03_03 module级别开始 function级别开始 PASSED function级别结束 class级别结束
testcases/test_03.py::test_03_04 function级别开始 PASSED function级别结束 module级别结束
testcases/test_03.py::test_03_05 不写范围function级别开始 PASSED 不写范围function级别结束 module级别结束
|
9.pytest.ini 配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| [pytest]
testpaths = tests
python_files = test_*.py
testpaths =
addopts = -v
log_cli = true log_cli_level = INFO
cov = my_package cov_config = .coveragerc cov_report = term-missing:skip-covered
|