pytest 的基本使用方法
1、断言
在 unittest 单元测试框架中提供了丰富的断言方法,如 assertEqual()、assertIn()、assertTrue()、assertIs()等。pytest 单元测试框架并没有提供专门的断言方法,而是直接使用Python 的 assert 进行断言。 创建 test_assert.py 文件# -*- coding:utf-8 -*- # filename: test_assert.py # author: hello.yin # date: 2021/11/18 import pytest # 创建用于测试的功能函数 # 加法测试 def add(a, b): return a + b # 判断素数 def is_prime(n): if n < 2: return False else: for i in range(2, n): if n % i == 0: return False else: return True # 判断相等 def test_add1(): assert add(3, 5) == 8 # 判断大于 def test_add2(): assert add(5, 5) > 10 # 判断为True def test_isprime1(): assert is_prime(1) is False # 判断为False def test_isprime2(): assert is_prime(7) is True # 判断包含 def test_in(): a = "he" b = "hello" assert a in b # 判断不包含 def test_not_in(): a = "yin" b = "hello" assert a not in b if __name__ == "__main__": pytest.main()
执行结果:
(base_practice) D:\00test\base_practice\pyTest>pytest test_assert.py =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 6 items test_assert.py .F.... [100%] ==================================================================== FAILURES ===================================================================== ____________________________________________________________________ test_add2 ____________________________________________________________________ def test_add2(): > assert add(5, 5) > 10 E assert 10 > 10 E + where 10 = add(5, 5) test_assert.py:34: AssertionError ============================================================= short test summary info ============================================================= FAILED test_assert.py::test_add2 - assert 10 > 10 =========================================================== 1 failed, 5 passed in 0.06s ===========================================================上面的例子展示了 pytest 断言的用法,借助 Python 的运算符号和关键字即可轻松实现不同数据类型的断言。
2、Fixture
Fixture 通常用来对测试方法、测试函数、测试类和整个测试文件进行初始化或还原测试环境。创建 test_fixtures_01.py 文件。# -*- coding:utf -8 -*- # filename: /pyTest/test_fixtrue.py # author: hello.yin # date: 2021/11/18 # 功能函数 def mul(a, b): return a*b # 模块级别 def setup_module(module): print("===========setup_module=========") def teardown_module(module): print("============teardown_module========") # 函数级别 def setup_function(function): print("==========setup_founction=========") def teardown_function(function): print("==========setup_founction==========") # 用例级别 def setup(): print("==========setup=========") def teardown(): print("=========teardowm==========") # 测试用例 def test_35(): assert mul(3, 5) == 14 def test_45(): assert mul(4, 5) == 20这里主要用到模块级别和函数级别的 Fixture。
- setup_module/teardown_module:在当前文件中,在所有测试用例执行之前与之后执行。
- setup_function/teardown_function:在每个测试函数之前与之后执行。
- setup/teardown:在每个测试函数之前与之后执行。这两个方法同样可以作用于类方法。
执行结果:
(base_practice) D:\00test\base_practice\pyTest>pytest test_fixtrue01.py =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 2 items test_fixtrue01.py F. [100%] ==================================================================== FAILURES ===================================================================== _____________________________________________________________________ test_35 _____________________________________________________________________ def test_35(): > assert mul(3, 5) == 14 E assert 15 == 14 E + where 15 = mul(3, 5) test_fixtrue01.py:41: AssertionError -------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------- ===========setup_module========= ==========setup_founction========= ==========setup========= ------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------- =========teardowm========== ==========setup_founction========== ============================================================= short test summary info ============================================================= FAILED test_fixtrue01.py::test_35 - assert 15 == 14 =========================================================== 1 failed, 1 passed in 0.05s ===========================================================
pytest 是支持使用测试类的,同样必须以“Test”开头,注意首字母大写。在引入测试类的情况下,Fixture 的用法如下。创建 test_fixtures_02.py 文件。
# -*- coding:utf-8 -*- # filename: test_fixtrue02.py # author: hello.yin # date: 2021/11/18 # 功能函数 def mul(a, b): return a*b # 判断函数类 class TestMul: @classmethod def setup_class(cls): print("===============setup_class===============") @classmethod def teardown_class(cls): print("===============teardown_class==============") @staticmethod def setup_method(self): print("==============setup_method=================") @staticmethod def teardown_method(self): print("==============teardown_method==============") @staticmethod def setup(): print("==========setup=============") @staticmethod def teardown(): print("===========teardown============") # 测试用例 def test_mul35(self): assert mul(3, 5) == 16 def test_mul45(self): assert mul(4, 5) == 20这里主要用到类级别和方法级别的 Fixture。
- setup_class/teardown_class :在当前测试类的开始与结束时执行。
- setup_method/teardown_method :在每个测试方法开始与结束时执行。
- setup/teardown :在每个测试方法开始与结束时执行,同样可以作用于测试函数。
(base_practice) D:\00test\base_practice\pyTest>pytest test_fixtrue02.py =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 2 items test_fixtrue02.py F. [100%] ==================================================================== FAILURES ===================================================================== _______________________________________________________________ TestMul.test_mul35 ________________________________________________________________ self = <pyTest.test_fixtrue02.TestMul object at 0x0000026A7E40D108> def test_mul35(self): > assert mul(3, 5) == 16 E assert 15 == 16 E + where 15 = mul(3, 5) test_fixtrue02.py:41: AssertionError -------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------- ===============setup_class=============== ==============setup_method================= ==========setup============= ------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------- ===========teardown============ ==============teardown_method============== ============================================================= short test summary info ============================================================= FAILED test_fixtrue02.py::TestMul::test_mul35 - assert 15 == 16 =========================================================== 1 failed, 1 passed in 0.05s ===========================================================
3、 参数化
当一组测试用例有固定的测试数据时,就可以通过参数化的方式简化测试用例的编写。pytest 本身是支持参数化的,不需要额外安装插件。创建 test_parameterize.py 文件。# -*- coding:utf-8 -*- # filename: test_parameterized.py # author: hello.yin # date: 20221/11/18 import pytest import math @pytest.mark.parametrize( "base, exponent, expected", ((2, 2, 4), (3, 3, 9), (2, 3, 8), (0, 9, 0)), ids=["case1", "case2", "case3", "case4"]) def test_row(base, exponent, expected): assert math.pow(base, exponent) == expected
用法与 unittest 的参数化插件类似,通过 pytest.mark.parametrize()方法设置参数。“base,exponent,expected”用来定义参数的名称。通过数组定义参数时,每一个元组都是一条测试用例使用的测试数据。ids 参数默认为 None,用于定义测试用例的名称。math 模块的 pow()方法用于计算 xy(x 的 y 次方)的值。
运行结果如下:(base_practice) D:\00test\base_practice\pyTest>pytest -v test_parameterized.py =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- C:\Users\yzp\PycharmProjects\base_practice\Scripts\python.exe cachedir: .pytest_cache rootdir: D:\00test\base_practice\pyTest collected 4 items test_parameterized.py::test_row[case1] PASSED [ 25%] test_parameterized.py::test_row[case2] FAILED [ 50%] test_parameterized.py::test_row[case3] PASSED [ 75%] test_parameterized.py::test_row[case4] PASSED [100%] ==================================================================== FAILURES ===================================================================== _________________________________________________________________ test_row[case2] _________________________________________________________________ base = 3, exponent = 3, expected = 9 @pytest.mark.parametrize( "base, exponent, expected", ((2, 2, 4), (3, 3, 9), (2, 3, 8), (0, 9, 0)), ids=["case1", "case2", "case3", "case4"]) def test_row(base, exponent, expected): > assert math.pow(base, exponent) == expected E assert 27.0 == 9 E +27.0 E -9 test_parameterized.py:18: AssertionError ============================================================= short test summary info ============================================================= FAILED test_parameterized.py::test_row[case2] - assert 27.0 == 9 =========================================================== 1 failed, 3 passed in 0.04s ===========================================================
4、 运行测试
pytest 提供了丰富的参数运行测试用例,在前面的例子中已经使用到一些参数,例如,“-s”参数用于关闭捕捉,从而输出打印信息;“-v”参数用于增加测试用例冗长。 通过“pytest --help”可以查看帮助:pytest––helppytest 提供的参数比较多,下面只介绍常用的参数:
4.1.运行名称中包含某字符串的测试用例
在前面内容test_assert.py 文件,其中有 42条是关于 add()功能的,并且在测试用例的名称上包含了“add”字符串,因此这里可以通过“-k”来指定在名称中包含“add”的测试用例。(base_practice) D:\00test\base_practice\pyTest>pytest -k add test_assert.py =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 6 items / 4 deselected / 2 selected test_assert.py .F [100%] ==================================================================== FAILURES ===================================================================== ____________________________________________________________________ test_add2 ____________________________________________________________________ def test_add2(): > assert add(5, 5) > 10 E assert 10 > 10 E + where 10 = add(5, 5) test_assert.py:34: AssertionError ============================================================= short test summary info ============================================================= FAILED test_assert.py::test_add2 - assert 10 > 10 ==================================================== 1 failed, 1 passed, 4 deselected in 0.04s ====================================================
4.2.减少测试的运行冗长
这一次运行日志少了很多信息,“-q”用来减少测试运行的冗长;也可以使用“--quiet”代替(base_practice) D:\00test\base_practice\pyTest>pytest -q test_assert.py .F.... [100%] ==================================================================== FAILURES ===================================================================== ____________________________________________________________________ test_add2 ____________________________________________________________________ def test_add2(): > assert add(5, 5) > 10 E assert 10 > 10 E + where 10 = add(5, 5) test_assert.py:34: AssertionError ============================================================= short test summary info ============================================================= FAILED test_assert.py::test_add2 - assert 10 > 10 1 failed, 5 passed in 0.03s
4.3. 如果出现一条测试用例失败,则退出测试
这在测试用例的调试阶段是有用的,当出现一条失败的测试用例时,应该先通过调试让这条测试用例运行通过,而不是继续执行后面的测试用例。(base_practice) D:\00test\base_practice\pyTest>pytest -x test_assert.py =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 6 items test_assert.py .F ==================================================================== FAILURES ===================================================================== ____________________________________________________________________ test_add2 ____________________________________________________________________ def test_add2(): > assert add(5, 5) > 10 E assert 10 > 10 E + where 10 = add(5, 5) test_assert.py:34: AssertionError ============================================================= short test summary info ============================================================= FAILED test_assert.py::test_add2 - assert 10 > 10 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! =========================================================== 1 failed, 1 passed in 0.04s ===========================================================
4.4.运行测试目录
测试目录既可以指定相对路径(如 ./test_dir ) , 也 可以指定绝对路径(如D:\00test\base_practice\pyTest)(base_practice) D:\00test\base_practice\pyTest>pytest D:\00test\base_practice\pyTest =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 15 items test_assert.py .F.... [ 40%] test_fixtrue01.py F. [ 53%] test_fixtrue02.py F. [ 66%] test_parameterized.py .F.. [ 93%] test_sample.py F [100%] ==================================================================== FAILURES ===================================================================== ____________________________________________________________________ test_add2 ____________________________________________________________________ def test_add2(): > assert add(5, 5) > 10 E assert 10 > 10 E + where 10 = add(5, 5) test_assert.py:34: AssertionError _____________________________________________________________________ test_35 _____________________________________________________________________ def test_35(): > assert mul(3, 5) == 14 E assert 15 == 14 E + where 15 = mul(3, 5) test_fixtrue01.py:41: AssertionError -------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------- ===========setup_module========= ==========setup_founction========= ==========setup========= ------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------- =========teardowm========== ==========setup_founction========== _______________________________________________________________ TestMul.test_mul35 ________________________________________________________________ self = <pyTest.test_fixtrue02.TestMul object at 0x00000276153A5F88> def test_mul35(self): > assert mul(3, 5) == 16 E assert 15 == 16 E + where 15 = mul(3, 5) test_fixtrue02.py:41: AssertionError -------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------- ===============setup_class=============== ==============setup_method================= ==========setup============= ------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------- ===========teardown============ ==============teardown_method============== _________________________________________________________________ test_row[case2] _________________________________________________________________ base = 3, exponent = 3, expected = 9 @pytest.mark.parametrize( "base, exponent, expected", ((2, 2, 4), (3, 3, 9), (2, 3, 8), (0, 9, 0)), ids=["case1", "case2", "case3", "case4"]) def test_row(base, exponent, expected): > assert math.pow(base, exponent) == expected E assert 27.0 == 9 E + where 27.0 = <built-in function pow>(3, 3) E + where <built-in function pow> = math.pow test_parameterized.py:18: AssertionError ___________________________________________________________________ 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_assert.py::test_add2 - assert 10 > 10 FAILED test_fixtrue01.py::test_35 - assert 15 == 14 FAILED test_fixtrue02.py::TestMul::test_mul35 - assert 15 == 16 FAILED test_parameterized.py::test_row[case2] - assert 27.0 == 9 FAILED test_sample.py::test_answer - assert 4 == 5 ========================================================== 5 failed, 10 passed in 0.13s ===========================================================
4.5.指定特定类或方法执行
这里指定运行 test_fixtures_02.py 文件中 TestMul 类下的test_mul35()方法,文件名、类名和方法名之间用“::”符号分隔(base_practice) D:\00test\base_practice\pyTest>pytest test_fixtrue02.py::TestMul::test_mul35 =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 1 item test_fixtrue02.py F [100%] ==================================================================== FAILURES ===================================================================== _______________________________________________________________ TestMul.test_mul35 ________________________________________________________________ self = <pyTest.test_fixtrue02.TestMul object at 0x0000027E785C9808> def test_mul35(self): > assert mul(3, 5) == 16 E assert 15 == 16 E + where 15 = mul(3, 5) test_fixtrue02.py:41: AssertionError -------------------------------------------------------------- Captured stdout setup -------------------------------------------------------------- ===============setup_class=============== ==============setup_method================= ==========setup============= ------------------------------------------------------------ Captured stdout teardown ------------------------------------------------------------- ===========teardown============ ==============teardown_method============== ===============teardown_class============== ============================================================= short test summary info ============================================================= FAILED test_fixtrue02.py::TestMul::test_mul35 - assert 15 == 16 ================================================================ 1 failed in 0.04s ================================================================
4.6.通过 main()方法运行测试
import pytest if __name__ == '__main__': pytest.main(['-s', './test_dir'])创建 run_tests.py 文件,在文件中通过数组指定参数,每个参数为数组中的一个元素。
5、生成测试报告
pytest 支持生成多种格式的测试报告。5.1 生成 JUnit XML 文件
> pytest ./test_dir --junit-xml=./report/log.xml
XML 类型的日志主要用于存放测试结果,方便我们利用里面的数据定制自己的测试报告。XML 格式的测试报告如图所示。
执行结果:
(base_practice) D:\00test\base_practice\pyTest>pytest test_assert.py --junit-xml=./report/log.xml =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 rootdir: D:\00test\base_practice\pyTest collected 6 items test_assert.py .F.... [100%] ==================================================================== FAILURES ===================================================================== ____________________________________________________________________ test_add2 ____________________________________________________________________ def test_add2(): > assert add(5, 5) > 10 E assert 10 > 10 E + where 10 = add(5, 5) test_assert.py:34: AssertionError ---------------------------------------- generated xml file: D:\00test\base_practice\pyTest\report\log.xml ---------------------------------------- ============================================================= short test summary info ============================================================= FAILED test_assert.py::test_add2 - assert 10 > 10 =========================================================== 1 failed, 5 passed in 0.06s ===========================================================
6、conftest.py
conftest.py 是 pytest 特有的本地测试配置文件,既可以用来设置项目级别的 Fixture,也可以用来导入外部插件,还可以用来指定钩子函数。 创建 test_project/conftest.py 测试配置文件。 需要说明的是,conftest.py 只作用于它所在的目录及子目录。创建 test_project/conftest.py 测试配置文件
import pytest @pytest.fixture() def test_url(): return "https://www.baidu.com"
创建 test_project/test_sub.py 测试用例文件
def test_baidu(test_url): print(test_url)这里创建的函数可以直接调用 conftest.py 文件中的 test_url()钩子函数,测试结果如下
(base_practice) D:\00test\base_practice\pyTest>pytest -v -s test_project\ =============================================================== test session starts =============================================================== platform win32 -- Python 3.7.4, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- C:\Users\yzp\PycharmProjects\base_practice\Scripts\python.exe cachedir: .pytest_cache rootdir: D:\00test\base_practice\pyTest collected 1 item test_project/test_sub.py::test_baidu https://www.baidu.com PASSED ================================================================ 1 passed in 0.01s ================================================================