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––help
pytest 提供的参数比较多,下面只介绍常用的参数:
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 ================================================================