pytest
- pytest认知
- pytest第一个简单例子
- pytest 使用方法
- 断言
- Fixture
- 参数化
- 运行测试
- 查看帮助
- 运行名称中包含某字符串的测试用例
- 减少测试的运行冗长
- 如果出现一条测试用例失败,则退出测试
- 运行测试目录
- 指定特定类或方法执行
- 通过main()方法运行测试
- 运行所有用mark修饰的测试用例
- 生成测试报告
- pytest扩展
- pytest-html
- pytest-rerunfailures
- pytest-parallel扩展
pytest认知
pytest是Python的一个第三方单元测试框架,提供了更加丰富的扩展,更加简单、灵活,弥补了unittest在做web自动化测试的一些不足。
pytest支持pip安装
1 | pip install -U pytest |
查看pytest版本
1 | pytest --version |
对于pytest学习,可以参考:
- 官方文档
- pytest中文文档
- pytest – 中文文档
pytest第一个简单例子
- 首先通过pytest编写一个简单的测试例子,test_sample.py
1 2 3 4 5 | def inc(x): return x + 1 def test_answer(): assert inc(3) == 5 |
- 切换到测试用例目录下,执行【pytest】命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | (py3_heima) D:\zhenghou\python_learning>cd test_pytest (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest =============================================================================== test session starts =============================================================================== platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 rootdir: D:\zhenghou\python_learning\test_pytest collected 1 item test_sample.py F [100%] ==================================================================================== FAILURES ===================================================================================== ___________________________________________________________________________________ test_answer ___________________________________________________________________________________ def test_answer(): > assert inc(3) == 5 E assert 4 == 5 E + where 4 = inc(3) test_sample.py:13: AssertionError ============================================================================= short test summary info ============================================================================= FAILED test_sample.py::test_answer - assert 4 == 5 ================================================================================ 1 failed in 0.46s ================================================================================ |
- pytest更加简单,不需要想unittest一样必须创建测试类
- 使用assert断言
- pytest的测试文件和测试函数必须以【test】开头
此外,pytest也可以使用像unittest一样,通过main()方法执行测试用例
1 2 3 4 5 6 7 8 9 10 11 | import pytest def inc(x): return x + 1 def test_answer(): assert inc(3) == 5 if __name__ == "__main__": pytest.main() |
在一个类中执行多组测试
1 2 3 4 5 6 7 8 | class TestClass: def test_one(self): x = "this" assert "h" in x def test_two(self): x = "hello" assert hasattr(x, "check") |
进入test_class所在目录,执行【test_class.py】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_class.py .F [100%] ==================================================================================== FAILURES ===================================================================================== _______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________ self = <test_pytest.test_class.TestClass object at 0x000002D1ACA22780> def test_two(self): x = "hello" > assert hasattr(x, "check") E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:16: AssertionError ============================================================================= short test summary info ============================================================================= FAILED test_class.py::TestClass::test_two - AssertionError: assert False 1 failed, 1 passed in 0.38s |
pytest 使用方法
断言
pytest单元测试框架没有提供专门的断言方法,而是直接使用Python的asser进行断言。
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 42 43 44 45 46 47 48 49 50 51 52 53 54 | # 计算a + b def add(a, b): return a + b # 判断是否为素数 def is_prime(n): if n <= 1: return False else: for i in range(2, n): if n % i == 0: return False return True # 测试相等 def test_add_1(): assert add(3, 4) == 7 # 测试不相等 def test_add_2(): assert add(5, 8) != 12 # 测试小于等于 def test_add_3(): assert add(5, 8) <= 10 # 测试大于等于 def test_add_4(): assert add(4, 5) >= 3 # 测试包含于 def test_in(): assert "h" in "hello" # 测试不包含 def test_not_in(): assert "he" not in "hello" # 测试是否为True def test_true_1(): assert is_prime(1) # 测试是否为True def test_true_2(): assert is_prime(1) is True # 测试是否为True def test_true_3(): assert is_prime(1) is not True # 测试是否为False def test_false(): assert is_prime(1) is False |
Fixture
Fixture通常用来对测试方法、测试函数、测试类和整个测试文件进行初始化或还原测试环境
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 | # 功能函数:计算两个数相加 def add(a, b): return a + b # ==============fixture======================= def setup_module(module): print("setup_module=========================>") def teardown_module(module): print("teardown_module==========================>") def setup_function(function): print("setup_function=====================>") def teardown_function(function): print("teardown_function===========================>") def setup(): print("setup=================================>") def teardown(): print("teardown=================================>") # 测试用例 def test_add_2_1(): assert add(2, 1) == 3 def test_add_gg_aa(): assert add("gg", "aa") == "ag" |
- setup_module/teardown_module: 在当前文件中,在所有测试用例执行之前与之后执行
- setup_function/teardown_function: 在每个测试函数之前与之后执行
- 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 | # 功能函数:计算两个数相加 def add(a, b): return a + b class TestAdd: @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 setup(self): print("setup=====================>") def teardown(self): print("teardown=============================>") # 测试用例 def test_number_3_4(self): print("test add number 3 and 4") assert add(3, 4) == 7 def test_string_gg_aa(self): print("test add string gg and aa") assert add("gg", "aa") == "ga" |
- setup_class/teardown_class: 在当前测试类的开始与结束时执行
- setup_method/teardown_method: 在每个测试方法开始与结束执行
- setup/teardown: 在每个测试方法开始与结束时执行,同样可以用于测试函数
参数化
当一组测试用例有固定的测试数据是,可以通过参数化的方式简化测试用例书写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import pytest import math @pytest.mark.parametrize( "base, exponent, expected", [(2, 2, 4), (2, 3, 8), (1, 9, 1), (0, 9, 0)], ids=["case1", "case2", "case3", "case4"] ) def test_pow(base, exponent, expected): assert math.pow(base, exponent) == expected |
"base, exponent, expected" : 用于定义参数的名称ids : 默认为None,用于定义测试用例的名称
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 | (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_parametrize.py =============================================================================== test session starts =============================================================================== platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe cachedir: .pytest_cache rootdir: D:\zhenghou\python_learning\test_pytest collected 4 items test_parametrize.py::test_pow[case1] PASSED [ 25%] test_parametrize.py::test_pow[case2] PASSED [ 50%] test_parametrize.py::test_pow[case3] PASSED [ 75%] test_parametrize.py::test_pow[case4] PASSED [100%] ================================================================================ 4 passed in 0.16s ================================================================================ |
运行测试
您可以从命令行通过python解释器调用测试:
1 | python -m pytest [...] |
查看帮助
1 | pytest -h | --help |
运行名称中包含某字符串的测试用例
1 2 3 4 5 6 7 8 | -k EXPRESSION only run tests which match the given substring expression. An expression is a python evaluatable expression where all names are substring-matched against test names and their parent classes. Example: -k 'test_method or test_other' matches all test functions and classes whose name contains 'test_method' or 'test_other', while -k 'not test_method' matches those that don't contain 'test_method' in their names. -k 'not test_method and not test_other' will eliminate the matches. Additionally keywords are matched to classes and functions containing extra names in their 'extra_keyword_matches' set, as well as functions which have names assigned directly to them. The matching is case-insensitive. |
使用
1 | pytest -k add test_assert.py |
执行【test_assert.py】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -k add -v test_assert.py =============================================================================== test session starts =============================================================================== platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe cachedir: .pytest_cache rootdir: D:\zhenghou\python_learning\test_pytest collected 10 items / 6 deselected / 4 selected test_assert.py::test_add_1 PASSED [ 25%] test_assert.py::test_add_2 PASSED [ 50%] test_assert.py::test_add_3 FAILED [ 75%] test_assert.py::test_add_4 PASSED [100%] ==================================================================================== FAILURES ===================================================================================== ___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________ def test_add_3(): > assert add(5, 8) <= 10 E assert 13 <= 10 E + where 13 = add(5, 8) test_assert.py:33: AssertionError ============================================================================= short test summary info ============================================================================= FAILED test_assert.py::test_add_3 - assert 13 <= 10 ==================================================================== 1 failed, 3 passed, 6 deselected in 0.24s ==================================================================== |
减少测试的运行冗长
1 | -q, --quiet decrease verbosity. |
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 42 43 | (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_assert.py ..F..FFF.. [100%] ==================================================================================== FAILURES ===================================================================================== ___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________ def test_add_3(): > assert add(5, 8) <= 10 E assert 13 <= 10 E + where 13 = add(5, 8) test_assert.py:33: AssertionError ___________________________________________________________________________________ test_not_in ___________________________________________________________________________________ def test_not_in(): > assert "he" not in "hello" E AssertionError: assert 'he' not in 'hello' E 'he' is contained here: E hello E ? ++ test_assert.py:45: AssertionError ___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________ def test_true_1(): > assert is_prime(1) E assert False E + where False = is_prime(1) test_assert.py:49: AssertionError ___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________ def test_true_2(): > assert is_prime(1) is True E assert False is True E + where False = is_prime(1) test_assert.py:53: AssertionError ============================================================================= short test summary info ============================================================================= FAILED test_assert.py::test_add_3 - assert 13 <= 10 FAILED test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello' FAILED test_assert.py::test_true_1 - assert False FAILED test_assert.py::test_true_2 - assert False is True 4 failed, 6 passed in 0.27s |
如果出现一条测试用例失败,则退出测试
使用
1 | -x, --exitfirst exit instantly on first error or failed test. |
执行测试用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -x test_assert.py =============================================================================== test session starts =============================================================================== platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 rootdir: D:\zhenghou\python_learning\test_pytest collected 10 items test_assert.py ..F ==================================================================================== FAILURES ===================================================================================== ___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________ def test_add_3(): > assert add(5, 8) <= 10 E assert 13 <= 10 E + where 13 = add(5, 8) test_assert.py:33: AssertionError ============================================================================= short test summary info ============================================================================= FAILED test_assert.py::test_add_3 - assert 13 <= 10 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! =========================================================================== 1 failed, 2 passed in 0.21s =========================================================================== |
1 2 3 | pytest --maxfail=2 --maxfail=num exit after first num failures or errors. |
运行测试目录
测试目录既可以指定相对路径,也可以使用绝对路径
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | (py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest =============================================================================== test session starts =============================================================================== platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 rootdir: D:\zhenghou\python_learning collected 19 items test_pytest\test_assert.py ..F..FFF.. [ 52%] test_pytest\test_class.py .F [ 63%] test_pytest\test_fixture.py .F [ 73%] test_pytest\test_parametrize.py .... [ 94%] test_pytest\test_sample.py F [100%] ==================================================================================== FAILURES ===================================================================================== ___________________________________________________________________________________ test_add_3 ____________________________________________________________________________________ def test_add_3(): > assert add(5, 8) <= 10 E assert 13 <= 10 E + where 13 = add(5, 8) test_pytest\test_assert.py:33: AssertionError ___________________________________________________________________________________ test_not_in ___________________________________________________________________________________ def test_not_in(): > assert "he" not in "hello" E AssertionError: assert 'he' not in 'hello' E 'he' is contained here: E hello E ? ++ test_pytest\test_assert.py:45: AssertionError ___________________________________________________________________________________ test_true_1 ___________________________________________________________________________________ def test_true_1(): > assert is_prime(1) E assert False E + where False = is_prime(1) test_pytest\test_assert.py:49: AssertionError ___________________________________________________________________________________ test_true_2 ___________________________________________________________________________________ def test_true_2(): > assert is_prime(1) is True E assert False is True E + where False = is_prime(1) test_pytest\test_assert.py:53: AssertionError _______________________________________________________________________________ TestClass.test_two ________________________________________________________________________________ self = <test_pytest.test_class.TestClass object at 0x00000243DBAF00B8> def test_two(self): x = "hello" > assert hasattr(x, "check") E AssertionError: assert False E + where False = hasattr('hello', 'check') test_pytest\test_class.py:16: AssertionError ____________________________________________________________________________ TestAdd.test_string_gg_aa ____________________________________________________________________________ self = <test_pytest.test_fixture.TestAdd object at 0x00000243DBAF0470> def test_string_gg_aa(self): print("test add string gg and aa") > assert add("gg", "aa") == "ga" E AssertionError: assert 'ggaa' == 'ga' E - ga E + ggaa test_pytest\test_fixture.py:68: AssertionError ------------------------------------------------------------------------------ Captured stdout setup ------------------------------------------------------------------------------ setup_method==================> setup=====================> ------------------------------------------------------------------------------ Captured stdout call ------------------------------------------------------------------------------- test add string gg and aa ---------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------- teardown=============================> teardown_method==========================> teardown_class====================> ___________________________________________________________________________________ test_answer ___________________________________________________________________________________ def test_answer(): > assert inc(3) == 5 E assert 4 == 5 E + where 4 = inc(3) test_pytest\test_sample.py:14: AssertionError ============================================================================= short test summary info ============================================================================= FAILED test_pytest/test_assert.py::test_add_3 - assert 13 <= 10 FAILED test_pytest/test_assert.py::test_not_in - AssertionError: assert 'he' not in 'hello' FAILED test_pytest/test_assert.py::test_true_1 - assert False FAILED test_pytest/test_assert.py::test_true_2 - assert False is True FAILED test_pytest/test_class.py::TestClass::test_two - AssertionError: assert False FAILED test_pytest/test_fixture.py::TestAdd::test_string_gg_aa - AssertionError: assert 'ggaa' == 'ga' FAILED test_pytest/test_sample.py::test_answer - assert 4 == 5 ========================================================================== 7 failed, 12 passed in 0.37s =========================================================================== |
指定特定类或方法执行
每个收集的测试都被分配一个唯一的 nodeid 它由模块文件名和诸如类名、函数名和参数化参数等说明符组成,用 :: 字符。
在模块内运行特定测试:
1 | pytest test_mod.py::test_func |
在命令行中指定测试方法的另一个示例:
1 | pytest test_mod.py::TestClass::test_method |
例如,指定运行test_fixture.py文件中TestAdd类下的test_number_3_4方法
1 2 3 4 5 6 7 8 9 | py3_heima) D:\zhenghou\python_learning\test_pytest>pytest test_fixture.py::TestAdd::test_number_3_4 =============================================================================== test session starts =============================================================================== platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 rootdir: D:\zhenghou\python_learning\test_pytest collected 1 item test_fixture.py . [100%] ================================================================================ 1 passed in 0.02s ================================================================================ |
通过main()方法运行测试
1 2 3 4 | import pytest if __name__ == "__main__": pytest.main("-s", "./test_dir") |
运行所有用mark修饰的测试用例
Run tests by marker expressions
1 | pytest -m slow |
将运行所有用 @pytest.mark.slow 装饰符。
生成测试报告
- 生成JUnit XML文件
1 | (py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --junit-xml=./report/log.xml |

- 生成在线测试报告
1 | (py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --pastebin=all |
执行上述代码会生成一个session-log链接,使用浏览器打开,会得到一张HTML格式的测试报告

pytest扩展
pytest-html
pytest-html可以生成HTML格式的测试报告,还支持测试用例失败截图,对于web自动化测试来说非常有用。
安装
1 | pip install pytest-html |
运行测试用例
1 | (py3_heima) D:\zhenghou\python_learning>pytest ./test_pytest --html=./report/result.html |
执行结果

pytest-rerunfailures
pytest-rerunfailures可以在测试用例失败时进行重试
安装
1 | pip install pytest-rerunfailures |
安装完成,通过【–reruns】参数设置测试用例运行失败后的重试次数
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 | (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -v test_sample.py --reruns 3 =============================================================================== test session starts =============================================================================== platform win32 -- Python 3.6.4, pytest-5.4.3, py-1.8.2, pluggy-0.13.1 -- d:\env_director\envs\py3_heima\scripts\python.exe cachedir: .pytest_cache metadata: {'Python': '3.6.4', 'Platform': 'Windows-10-10.0.16299-SP0', 'Packages': {'pytest': '5.4.3', 'py': '1.8.2', 'pluggy': '0.13.1'}, 'Plugins': {'html': '2.1.1', 'metadata': '1.9.0', 'rerunfailures': '9.0'}, 'JAVA_HOME': 'F:\\java8'} rootdir: D:\zhenghou\python_learning\test_pytest plugins: html-2.1.1, metadata-1.9.0, rerunfailures-9.0 collected 1 item test_sample.py::test_answer RERUN [100%] test_sample.py::test_answer RERUN [100%] test_sample.py::test_answer RERUN [100%] test_sample.py::test_answer FAILED [100%] ==================================================================================== FAILURES ===================================================================================== ___________________________________________________________________________________ test_answer ___________________________________________________________________________________ def test_answer(): > assert inc(3) == 5 E assert 4 == 5 E +4 E -5 test_sample.py:14: AssertionError ============================================================================= short test summary info ============================================================================= FAILED test_sample.py::test_answer - assert 4 == 5 =========================================================================== 1 failed, 3 rerun in 0.09s ============================================================================ |
pytest-parallel扩展
pytest-parallel扩展可以实现测试用例的并行运行
安装
1 | pip install pytest-parallel |
创建测试用例
1 2 3 4 5 6 7 8 9 10 11 | import time def test_01(): time.sleep(3) def test_02(): time.sleep(5) def test_03(): time.sleep(6) |
不使用线程执行测试用例
1 2 3 | (py3_heima) D:\zhenghou\python_learning\test_pytest>pytest -q test_parallel.py ... [100%] 3 passed in 14.18s |
使用【–test-per-worker】指定线程数,【auto】为自动分配
1 | pytest -q test_parallel.py --tests-per-worker auto |
其他用法
1 2 3 4 5 6 7 | pytest --workers 2 # run 2 workers with 1 test per worker at a time pytest --workers auto # run 4 workers(4核) with 1 test per worker pytest --tests-per-worker 4 # run 1 worker with 4 tests at a time pytest --tests-per-worker auto # runs 1 worker with up to 50 test2 at a time pytest --workers 2 --tests-per-worker auto |
而pytest-parallel支持python3.6及以上版本,如果是想做多进程并发的需要在linux平台或mac上做,windows上不起作用即(workers永远=1),如果是做多线程的Linux/Mac/Windows平台都支持,进程数为workers设置的值
参考:python-pytest使用(4)-多线程多进程运行