简介
pytest是动态编程语言Python专用的测试框架,它具有易于上手、功能强大、可扩展性好、兼容性强、效率高、第三方插件丰富等特点。
功能特征:
编写规则:
自动发现规则:
官方文档:https://docs.pytest.org/en/latest/contents.html
安装
打开bash命令行,运行以下命令:
pip install -U pytest
检查是否安装了正确的版本:
$ pytest --version
pytest 6.1.2
示例
创建一个简单的测试函数:
# test_sample.py # 被测功能 def func(x): return x + 1 # 测试成功 def test_pass(): assert func(3) == 4 # 测试失败 def test_fail(): assert func(3) == 5
现在开始执行测试功能:
E:\workspace-py\Pytest>pytest ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 2 items test_sample.py .F [100%] =============================================================================== FAILURES ================================================================================ _______________________________________________________________________________ test_fail _______________________________________________________________________________ def test_fail(): > assert func(3) == 5 E assert 4 == 5 E + where 4 = func(3) test_sample.py:16: AssertionError ======================================================================== short test summary info ======================================================================== FAILED test_sample.py::test_fail - assert 4 == 5 ====================================================================== 1 failed, 1 passed in 0.16s ======================================================================
标记
默认情况下,pytest 会递归查找当前目录下所有以 test 开始或结尾的 Python 脚本,并执行文件内的所有以 test 开始或结束的函数和方法。
1、如果你想指定运行测试用例,可以通过 ::
显式标记(文件名::
类名::方法名
)。
E:\workspace-py\Pytest>pytest test_sample.py::test_pass ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 1 item test_sample.py . [100%] =========================================================================== 1 passed in 0.05s ===========================================================================
2、如果你想选择一些测试用例,可以使用 -k
模糊匹配。
E:\workspace-py\Pytest>pytest -k pass test_sample.py ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 2 items / 1 deselected / 1 selected test_sample.py . [100%] ==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================
3、如果你想跳过个别测试用例,可以使用 pytest.mark.skip(),或者 pytest.mark.skipif(条件表达式)。
# 测试失败 @pytest.mark.skip() def test_fail(): assert func(3) == 5
E:\workspace-py\Pytest>pytest -v test_sample.py ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 -- c:\python37\python.exe cachedir: .pytest_cache metadata: {'Python': '3.7.3', 'Platform': 'Windows-7-6.1.7601-SP1', 'Packages': {'pytest': '6.0.2', 'py': '1.9.0', 'pluggy': '0.13.0'}, 'Plugins': {'allure-pytest': '2.8. 18', 'cov': '2.10.1', 'html': '2.1.1', 'metadata': '1.8.0', 'rerunfailures': '9.1', 'xdist': '2.1.0'}} rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 2 items test_sample.py::test_pass PASSED [ 50%] test_sample.py::test_fail SKIPPED [100%] ===================================================================== 1 passed, 1 skipped in 0.07s ======================================================================
4、如果你想捕捉一些异常,可以使用pytest.raises()。
# test_raises.py def test_raises(): with pytest.raises(TypeError) as e: connect('localhost', '6379') exec_msg = e.value.args[0] assert exec_msg == 'port type must be int'
5、如果你事先知道测试函数会执行失败,但又不想直接跳过,而是希望显示的提示,可以使用pytest.mark.xfail()。
# 测试失败 @pytest.mark.xfail() def test_fail(): assert func(3) == 5
E:\workspace-py\Pytest>pytest -k fail test_sample.py ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 2 items / 1 deselected / 1 selected test_sample.py x [100%] =================================================================== 1 deselected, 1 xfailed in 0.05s ====================================================================
6、如果你想对某个测试点进行多组数据测试,可以使用 pytest.mark.parametrize(argnames, argvalues) 参数化测试,即每组参数都独立执行一次测试。
注意:以往我们可以把这些参数写在测试函数内部进行遍历,但是当某组参数导致断言失败,测试则就终止了。
# 测试成功 @pytest.mark.parametrize('data', [1, 2, 3]) def test_pass(data): assert func(data) == 4
E:\workspace-py\Pytest>pytest -k pass test_sample.py ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 4 items / 1 deselected / 3 selected test_sample.py FF. [100%] =============================================================================== FAILURES ================================================================================ _____________________________________________________________________________ test_pass[1] ______________________________________________________________________________ data = 1 @pytest.mark.parametrize('data', [1, 2, 3]) def test_pass(data): > assert func(data) == 4 E assert 2 == 4 E + where 2 = func(1) test_sample.py:11: AssertionError _____________________________________________________________________________ test_pass[2] ______________________________________________________________________________ data = 2 @pytest.mark.parametrize('data', [1, 2, 3]) def test_pass(data): > assert func(data) == 4 E assert 3 == 4 E + where 3 = func(2) test_sample.py:11: AssertionError ======================================================================== short test summary info ======================================================================== FAILED test_sample.py::test_pass[1] - assert 2 == 4 FAILED test_sample.py::test_pass[2] - assert 3 == 4 =============================================================== 2 failed, 1 passed, 1 deselected in 0.17s ===============================================================
固件
固件(Fixture)是一些函数,pytest 会在执行测试函数之前(或之后)加载运行它们。
我们可以利用固件做任何事情,其中最常见的可能就是数据库的初始连接和最后关闭操作。
1、Pytest使用pytest.fixture()定义固件,为了在实际工程中可以更大程度上复用,我们更多的是使用文件conftest.py集中管理固件(pytest会自动调用)。
# conftest.py import pytest @pytest.fixture() def data(): return 3
# 测试成功 def test_pass(data): assert func(data) == 4
E:\workspace-py\Pytest>pytest -k pass test_sample.py ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 2 items / 1 deselected / 1 selected test_sample.py . [100%] ==================================================================== 1 passed, 1 deselected in 0.05s ====================================================================
2、Pytest 使用 yield 关键词将固件分为两部分,yield 之前的代码属于预处理,会在测试前执行;yield 之后的代码属于后处理,将在测试完成后执行。
# conftest.py import pytest @pytest.fixture() def db(): print('opened') yield print('closed')
# 测试成功 def test_pass(db): assert func(3) == 4
E:\workspace-py\Pytest>pytest -s -k pass test_sample.py ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 2 items / 1 deselected / 1 selected test_sample.py opened .closed ==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================
3、为了更精细化控制固件,pytest使用作用域来进行指定固件的使用范围。
# conftest.py import pytest @pytest.fixture(scope='function', autouse=True) def func_scope(): pass @pytest.fixture(scope='module', autouse=True) def mod_scope(): pass @pytest.fixture(scope='session') def sess_scope(): pass @pytest.fixture(scope='class') def class_scope(): pass
# 测试成功
@pytest.mark.usefixtures('sess_scope')
def test_pass(class_scope):
assert func(3) == 4
E:\workspace-py\Pytest>pytest --setup-show -k pass test_sample.py ========================================================================== test session starts ========================================================================== platform win32 -- Python 3.7.3, pytest-6.0.2, py-1.9.0, pluggy-0.13.0 rootdir: E:\workspace-py\Pytest plugins: allure-pytest-2.8.18, cov-2.10.1, html-2.1.1, metadata-1.8.0, rerunfailures-9.1, xdist-2.1.0 collected 2 items / 1 deselected / 1 selected test_sample.py SETUP S sess_scope SETUP M mod_scope SETUP C class_scope SETUP F func_scope test_sample.py::test_pass (fixtures used: class_scope, func_scope, mod_scope, sess_scope). TEARDOWN F func_scope TEARDOWN C class_scope TEARDOWN M mod_scope TEARDOWN S sess_scope ==================================================================== 1 passed, 1 deselected in 0.02s ====================================================================
4、内置固件: