Pytest
最后修改于 2024 年 1 月 29 日
Pytest 教程演示了如何使用 pytest 模块测试 Python 应用程序。
Python pytest
Pytest 是一个用于测试 Python 应用程序的 Python 库。它是 nose 和 unittest 的替代品。
pytest 安装
使用以下命令安装 pytest:
$ pip install pytest
这将安装 pytest 库。
pytest 测试发现约定
如果没有指定参数,则在 testpaths(如果已配置)或当前目录中的位置搜索测试文件。或者,可以使用目录、文件名或节点 ID 的任何组合作为命令行参数。
在选定的目录中,pytest 会查找 test_*.py 或 *_test.py 文件。在选定的文件中,pytest 会查找类外部以 test 开头的测试函数,以及 Test 开头的类(没有 __init__ 方法)内部以 test 开头的测试方法。
运行 pytest
不带参数时,pytest 会查看当前工作目录(或其他预先配置的目录)及其所有子目录中的测试文件,并运行它找到的测试代码。
$ pytest
运行当前目录中的所有测试文件。
$ pytest min_max_test.py
我们可以通过将文件名作为参数来运行特定的测试文件。
$ pytest min_max_test.py::test_min
可以通过在 :: 字符后提供函数名来运行特定的函数。
$ pytest -m smoke
可以使用标记来对测试进行分组。然后使用 pytest -m 运行标记的测试组。
$ pytest -k <expression>
此外,我们还可以使用表达式来运行与测试函数和类名称匹配的测试。
Python pytest 简单示例
在第一个示例中,我们将使用 pytest 测试两个简单的数学算法。
def max(values):
_max = values[0]
for val in values:
if val > _max:
_max = val
return _max
def min(values):
_min = values[0]
for val in values:
if val < _min:
_min = val
return _min
我们有一个包含自定义 max 和 min 函数的模块。
#!/usr/bin/python
import algo
def test_min():
values = (2, 3, 1, 4, 6)
val = algo.min(values)
assert val == 1
def test_max():
values = (2, 3, 1, 4, 6)
val = algo.max(values)
assert val == 6
测试文件 min_max_test.py 的名称中包含 test 单词。
def test_min(): values = (2, 3, 1, 4, 6) val = algo.min(values) assert val == 1
此外,测试函数 test_min 也包含 test 单词。我们使用 assert 关键字来测试算法的值。
$ pytest min_max_test.py ================================================= test session starts ================================================= platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 rootdir: C:\Users\Jano\Documents\pyprogs\pytest collected 2 items min_max_test.py .. [100%] ============================================== 2 passed in 0.03 seconds ===============================================
这是输出。有两个测试,并且都已成功通过。使用 pytest -v min_max_test.py 可以显示更详细的输出。
Pytest 跳过
使用 skip 装饰器,我们可以跳过指定的测试。跳过测试有多种原因;例如,数据库/在线服务暂时不可用,或者我们在 Windows 上跳过特定于 Linux 的测试。
#!/usr/bin/python
import algo
import pytest
@pytest.mark.skip
def test_min():
values = (2, 3, 1, 4, 6)
val = algo.min(values)
assert val == 1
def test_max():
values = (2, 3, 1, 4, 6)
val = algo.max(values)
assert val == 6
在示例中,test_min 被跳过。
$ pytest min_max_test.py ================================================= test session starts ================================================= platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 rootdir: C:\Users\Jano\Documents\pyprogs\pytest collected 2 items min_max_test.py s. [100%] ========================================= 1 passed, 1 skipped in 0.04 seconds =========================================
在测试文件名后面的输出中,s 表示跳过,. 表示通过。
Pytest 标记
我们可以使用标记将测试组织成单元。
#!/usr/bin/python
# pytest -m a marking.py
# pytest -m b marking.py
import pytest
@pytest.mark.a
def test_a1():
assert (1) == (1)
@pytest.mark.a
def test_a2():
assert (1, 2) == (1, 2)
@pytest.mark.a
def test_a3():
assert (1, 2, 3) == (1, 2, 3)
@pytest.mark.b
def test_b1():
assert "falcon" == "fal" + "con"
@pytest.mark.b
def test_b2():
assert "falcon" == f"fal{'con'}"
我们有两个由标记标识的测试组,a 和 b。这些单元通过 pytest -m a marking.py 和 pytest -m b marking.py 运行。
Pytest 参数化测试
通过参数化测试,我们可以为断言添加多个值。我们使用 @pytest.mark.parametrize 标记。
#!/usr/bin/python
import algo
import pytest
@pytest.mark.parametrize("data, expected", [((2, 3, 1, 4, 6), 1),
((5, -2, 0, 9, 12), -2), ((200, 100, 0, 300, 400), 0)])
def test_min(data, expected):
val = algo.min(data)
assert val == expected
@pytest.mark.parametrize("data, expected", [((2, 3, 1, 4, 6), 6),
((5, -2, 0, 9, 12), 12), ((200, 100, 0, 300, 400), 400)])
def test_max(data, expected):
val = algo.max(data)
assert val == expected
在示例中,我们使用多个输入数据测试了两个函数。
@pytest.mark.parametrize("data, expected", [((2, 3, 1, 4, 6), 1),
((5, -2, 0, 9, 12), -2), ((200, 100, 0, 300, 400), 0)])
def test_min(data, expected):
val = algo.min(data)
assert val == expected
我们将两个值传递给测试函数:数据和期望值。在我们的例子中,我们使用三个数据元组测试了 min 函数。
$ pytest parameterized.py ================================================= test session starts ================================================= platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 rootdir: C:\Users\Jano\Documents\pyprogs\pytest collected 6 items parametrized.py ...... [100%] ============================================== 6 passed in 0.03 seconds ===============================================
Pytest 输出显示有六次运行。
Pytest Fixtures
测试需要在已知对象集合的背景下运行。这个对象集合称为测试 Fixture。
def sel_sort(data):
if not isinstance(data, list):
vals = list(data)
else:
vals = data
size = len(vals)
for i in range(0, size):
for j in range(i+1, size):
if vals[j] < vals[i]:
_min = vals[j]
vals[j] = vals[i]
vals[i] = _min
return vals
...
对于这个例子,我们在 algo.py 模块中添加了一个选择排序算法。
#!/usr/bin/python
import algo
import pytest
@pytest.fixture
def data():
return [3, 2, 1, 5, -3, 2, 0, -2, 11, 9]
def test_sel_sort(data):
sorted_vals = algo.sel_sort(data)
assert sorted_vals == sorted(data)
我们使用 Fixture 来测试选择排序。
@pytest.fixture
def data():
return [3, 2, 1, 5, -3, 2, 0, -2, 11, 9]
我们的测试 Fixture 仅返回一些测试数据。请注意,我们通过其名称 data 引用此 Fixture。
def test_sel_sort(data): sorted_vals = algo.sel_sort(data) assert sorted_vals == sorted(data)
在 test_sel_sort 函数中,我们将 data Fixture 作为函数参数传递。
$ pytest fixtures.py ================================================= test session starts ================================================= platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 rootdir: C:\Users\Jano\Documents\pyprogs\pytest collected 1 item fixtures.py . [100%] ============================================== 1 passed in 0.02 seconds ===============================================
Pytest 布局
Python 测试可以以多种方式组织。测试可以集成到 Python 包中,也可以放在包外部。
集成测试
接下来,我们将展示如何在 Python 包中运行测试。
setup.py
utils
│ algo.py
│ srel.py
│ __init__.py
│
└───tests
algo_test.py
srel_test.py
__init__.py
我们有这样的包布局。测试位于包内的 tests 子目录中。
#!/usr/bin/python from setuptools import setup, find_packages setup(name="utils", packages=find_packages())
这是 setup.py。
def sel_sort(data):
if not isinstance(data, list):
vals = list(data)
else:
vals = data
size = len(vals)
for i in range(0, size):
for j in range(i+1, size):
if vals[j] < vals[i]:
_min = vals[j]
vals[j] = vals[i]
vals[i] = _min
return vals
def max(values):
_max = values[0]
for val in values:
if val > _max:
_max = val
return _max
def min(values):
_min = values[0]
for val in values:
if val < _min:
_min = val
return _min
这是 algo.py 文件。
def is_palindrome(val):
return val == val[::-1]
我们有另一个模块,其中包含一个函数来测试一个单词是否是回文。
#!/usr/bin/python
import utils.algo
import pytest
@pytest.fixture
def data():
return [3, 2, 1, 5, -3, 2, 0, -2, 11, 9]
def test_sel_sort(data):
sorted_vals = utils.algo.sel_sort(data)
assert sorted_vals == sorted(data)
def test_min():
values = (2, 3, 1, 4, 6)
val = utils.algo.min(values)
assert val == 1
def test_max():
values = (2, 3, 1, 4, 6)
val = utils.algo.max(values)
assert val == 6
这些是 utils.algo 模块的测试。请注意,我们使用了完整的模块名称。
#!/usr/bin/python
import utils.srel
import pytest
@pytest.mark.parametrize("word, expected", [('kayak', True),
('civic', True), ('forest', False)])
def test_palindrome(word, expected):
val = utils.srel.is_palindrome(word)
assert val == expected
这是 is_palindrome 函数的测试。
两个 __init__.py 文件都是空的。
$ pytest --pyargs utils ================================================= test session starts ================================================= platform win32 -- Python 3.7.0, pytest-5.0.1, py-1.8.0, pluggy-0.12.0 rootdir: C:\Users\Jano\Documents\pyprogs\pytest\structure collected 6 items utils\tests\algo_test.py ... [ 50%] utils\tests\srel_test.py ... [100%] ============================================== 6 passed in 0.06 seconds ===============================================
我们使用 pytest --pyargs utils 命令运行测试。
包外的测试
下一个示例展示了一个应用程序源布局,其中测试未集成到包中。
setup.py
src
└───utils
│ algo.py
│ srel.py
tests
algo_test.py
srel_test.py
在此布局中,测试位于源树之外。请注意,不需要 __init__.py 文件。
$ set PYTHONPATH=src $ pytest
我们设置 PYTHONPATH 并运行 pytest。
来源
在本文中,我们介绍了 Python pytest 库。
作者
列出所有 Python 教程。