Pytest 的常用内置插件详解
1. 什么是 Pytest 插件
1.1 插件的基本概念
插件(Plugin) 是 pytest 框架的核心机制之一,它允许扩展和定制 pytest 的功能。你可以把插件理解为 pytest 的”扩展包”,它们可以:
- 增强功能:添加新的命令行选项、钩子函数、fixture 等
- 改变行为:修改测试发现、执行、报告等行为
- 集成工具:与其他工具(如覆盖率工具、报告生成器等)集成
1.2 插件的分类
Pytest 的插件主要分为两类:
-
内置插件(Built-in Plugins):
- pytest 自带的插件,无需安装即可使用
- 例如:测试发现插件、断言重写插件、标记插件等
-
第三方插件(Third-party Plugins):
- 需要单独安装的插件
- 例如:pytest-cov(覆盖率)、pytest-html(HTML报告)等
1.3 为什么需要插件
场景 1:需要生成测试报告
# 没有插件的情况
def test_example():
assert 1 + 1 == 2
# 运行测试后,只能看到简单的文本输出
# 无法生成美观的 HTML 报告
使用插件后:
# 安装 pytest-html 插件
pip install pytest-html
# 运行测试并生成 HTML 报告
pytest --html=report.html
场景 2:需要查看代码覆盖率
# 没有插件的情况
# 无法知道测试覆盖了多少代码
使用插件后:
# 安装 pytest-cov 插件
pip install pytest-cov
# 运行测试并查看覆盖率
pytest --cov=myproject --cov-report=html
2. 如何查看和管理插件
2.1 查看已安装的插件
2.1.1 查看所有活跃插件
方法 1:使用 --trace-config 参数
pytest --trace-config
输出示例:
============================= test session starts ==============================
platform win32 -- Python 3.9.0, pytest-7.0.0
cachedir: .pytest_cache
rootdir: C:UsersAdministratorDesktoppython自动化测试
plugins:
- cov-4.0.0
- html-3.1.1
- xdist-3.0.0
active plugins:
- cacheprovider: /path/to/pytest_cacheprovider.py
- capture: /path/to/pytest_capture.py
- ...
方法 2:使用 -p 参数查看特定插件
# 查看某个插件是否已安装
pytest -p pytest_cov
2.1.2 查看插件版本
# 查看 pytest 版本和已安装的插件
pytest --version
# 或者使用 pip 查看
pip list | grep pytest
输出示例:
pytest 7.0.0
pytest-cov 4.0.0
pytest-html 3.1.1
pytest-xdist 3.0.0
2.2 安装插件
2.2.1 使用 pip 安装
# 安装单个插件
pip install pytest-html
# 安装多个插件
pip install pytest-html pytest-cov pytest-xdist
# 从 requirements.txt 安装
pip install -r requirements.txt
requirements.txt 示例:
pytest>=7.0.0
pytest-html>=3.1.0
pytest-cov>=4.0.0
pytest-xdist>=3.0.0
pytest-timeout>=2.1.0
2.2.2 验证安装
# 运行 pytest --trace-config 查看插件是否已加载
pytest --trace-config | grep html
# 或者直接运行插件提供的功能
pytest --html=test.html
2.3 禁用插件
有时候,你可能需要临时禁用某个插件:
# 禁用某个插件(使用 -p no:插件名)
pytest -p no:html
# 禁用多个插件
pytest -p no:html -p no:cov
# 在 pytest.ini 中永久禁用
pytest.ini 配置示例:
[pytest]
# 禁用 html 插件
addopts = -p no:html
2.4 插件配置文件
2.4.1 pytest.ini 配置
[pytest]
# 添加命令行选项
addopts =
--html=report.html
--cov=myproject
--cov-report=html
-v
# 指定测试目录
testpaths = tests
# 指定测试文件模式
python_files = test_*.py
# 指定测试类模式
python_classes = Test*
# 指定测试函数模式
python_functions = test_*
2.4.2 setup.cfg 配置
[tool:pytest]
addopts =
--html=report.html
--cov=myproject
-v
testpaths = tests
2.4.3 pyproject.toml 配置
[tool.pytest.ini_options]
addopts = [
"--html=report.html",
"--cov=myproject",
"-v"
]
testpaths = ["tests"]
3. Pytest 内置核心插件
3.1 测试发现插件(Test Discovery)
3.1.1 什么是测试发现
测试发现插件负责自动找到项目中的测试文件和测试函数。这是 pytest 的核心功能之一。
3.1.2 默认发现规则
文件命名规则:
test_*.py:以test_开头的 Python 文件*_test.py:以_test结尾的 Python 文件
函数命名规则:
test_*:以test_开头的函数
类命名规则:
Test*:以Test开头的类(不能有__init__方法)
3.1.3 示例
项目结构:
project/
├── test_math.py # 会被发现
├── math_test.py # 会被发现
├── test_example.py # 会被发现
├── example.py # 不会被发现
└── utils.py # 不会被发现
test_math.py:
# 这个文件会被发现
def test_addition():
assert 1 + 1 == 2
def test_subtraction():
assert 3 - 1 == 2
# 这个函数不会被当作测试
def helper_function():
return True
math_test.py:
# 这个文件也会被发现
def test_multiplication():
assert 2 * 3 == 6
运行测试:
# 自动发现所有测试
pytest
# 输出:
# test_math.py::test_addition PASSED
# test_math.py::test_subtraction PASSED
# math_test.py::test_multiplication PASSED
3.1.4 自定义发现规则
pytest.ini 配置:
[pytest]
# 自定义测试文件模式
python_files = test_*.py check_*.py
# 自定义测试类模式
python_classes = Test* Check*
# 自定义测试函数模式
python_functions = test_* check_*
3.2 断言重写插件(Assertion Rewriting)
3.2.1 什么是断言重写
断言重写是 pytest 的一个强大特性,它会在测试失败时显示详细的断言信息,而不需要你手动添加调试信息。
3.2.2 为什么需要断言重写
标准 assert 的问题:
# 标准 assert 在失败时只显示简单的错误
def test_example():
x = [1, 2, 3]
y = [1, 2, 4]
assert x == y # 失败时只显示:AssertionError
pytest 的断言重写:
# pytest 会自动显示详细的比较信息
def test_example():
x = [1, 2, 3]
y = [1, 2, 4]
assert x == y
失败输出:
AssertionError: assert [1, 2, 3] == [1, 2, 4]
At index 2 diff: 3 != 4
Use -v to get more diff
3.2.3 详细示例
示例 1:列表比较
def test_list_comparison():
expected = [1, 2, 3, 4, 5]
actual = [1, 2, 3, 5, 4]
assert expected == actual
失败输出:
AssertionError: assert [1, 2, 3, 4, 5] == [1, 2, 3, 5, 4]
At index 3 diff: 4 != 5
At index 4 diff: 5 != 4
示例 2:字典比较
def test_dict_comparison():
expected = {"name": "张三", "age": 25}
actual = {"name": "李四", "age": 25}
assert expected == actual
失败输出:
AssertionError: assert {'name': '张三', 'age': 25} == {'name': '李四', 'age': 25}
Omitting 1 identical items, use -v to show all
Differing items:
{'name': '张三'} != {'name': '李四'}
示例 3:字符串比较
def test_string_comparison():
expected = "Hello World"
actual = "Hello Python"
assert expected == actual
失败输出:
AssertionError: assert 'Hello World' == 'Hello Python'
- Hello World
+ Hello Python
? ^^^^
3.2.4 使用 -v 参数获取更多信息
# 使用 -v 参数查看更详细的差异
pytest -v test_example.py
3.3 标记插件(Marking)
3.3.1 什么是标记
标记(Mark)允许你给测试用例添加”标签”,然后可以基于这些标签来筛选和运行特定的测试。
3.3.2 内置标记
1. @pytest.mark.skip:跳过测试
import pytest
@pytest.mark.skip(reason="这个功能还没实现")
def test_new_feature():
assert False
# 无条件跳过
@pytest.mark.skip
def test_old_feature():
assert True
运行结果:
test_example.py::test_new_feature SKIPPED [1] 这个功能还没实现
test_example.py::test_old_feature SKIPPED [1]
2. @pytest.mark.skipif:条件跳过
import sys
import pytest
# 如果 Python 版本小于 3.8,跳过测试
@pytest.mark.skipif(sys.version_info < (3, 8), reason="需要 Python 3.8+")
def test_new_syntax():
# 使用 Python 3.8+ 的新特性
pass
# 如果操作系统是 Windows,跳过测试
@pytest.mark.skipif(sys.platform == "win32", reason="不支持 Windows")
def test_unix_only():
assert True
3. @pytest.mark.xfail:预期失败
import pytest
# 标记为预期失败(已知 bug)
@pytest.mark.xfail(reason="已知 bug,待修复")
def test_buggy_feature():
assert False # 这个测试会失败,但这是预期的
# 如果测试通过了,会显示 XPASS
@pytest.mark.xfail
def test_maybe_fixed():
assert True # 如果这个通过了,会显示 XPASS
运行结果:
test_example.py::test_buggy_feature XFAIL [1] 已知 bug,待修复
test_example.py::test_maybe_fixed XPASS [1]
3.3.3 自定义标记
定义标记:
pytest.ini 配置:
[pytest]
markers =
slow: 标记为慢速测试
integration: 标记为集成测试
smoke: 标记为冒烟测试
regression: 标记为回归测试
使用标记:
import pytest
@pytest.mark.slow
def test_slow_operation():
import time
time.sleep(5)
assert True
@pytest.mark.integration
def test_integration():
# 集成测试代码
assert True
@pytest.mark.smoke
def test_smoke():
# 冒烟测试代码
assert True
# 可以同时使用多个标记
@pytest.mark.slow
@pytest.mark.integration
def test_slow_integration():
assert True
运行特定标记的测试:
# 只运行标记为 slow 的测试
pytest -m slow
# 只运行标记为 integration 的测试
pytest -m integration
# 运行标记为 smoke 或 regression 的测试
pytest -m "smoke or regression"
# 运行标记为 slow 且 integration 的测试
pytest -m "slow and integration"
# 运行除了 slow 之外的所有测试
pytest -m "not slow"
3.4 参数化插件(Parametrization)
3.4.1 什么是参数化
参数化允许你使用不同的输入数据运行同一个测试函数,避免编写重复的测试代码。
3.4.2 基本用法
不使用参数化(重复代码):
def test_addition_1():
assert 1 + 1 == 2
def test_addition_2():
assert 2 + 2 == 4
def test_addition_3():
assert 3 + 3 == 6
使用参数化(简洁高效):
import pytest
@pytest.mark.parametrize("a, b, expected", [
(1, 1, 2),
(2, 2, 4),
(3, 3, 6),
])
def test_addition(a, b, expected):
assert a + b == expected
运行结果:
test_example.py::test_addition[1-1-2] PASSED
test_example.py::test_addition[2-2-4] PASSED
test_example.py::test_addition[3-3-6] PASSED
3.4.3 详细示例
示例 1:测试字符串方法
import pytest
@pytest.mark.parametrize("input_str, expected", [
("hello", "HELLO"),
("world", "WORLD"),
("pytest", "PYTEST"),
])
def test_upper(input_str, expected):
assert input_str.upper() == expected
示例 2:测试数学运算
import pytest
@pytest.mark.parametrize("x, y, operation, expected", [
(2, 3, "add", 5),
(5, 2, "subtract", 3),
(3, 4, "multiply", 12),
(10, 2, "divide", 5),
])
def test_calculator(x, y, operation, expected):
if operation == "add":
result = x + y
elif operation == "subtract":
result = x - y
elif operation == "multiply":
result = x * y
elif operation == "divide":
result = x / y
assert result == expected
示例 3:组合参数化
import pytest
# 第一个参数化
@pytest.mark.parametrize("x", [1, 2, 3])
# 第二个参数化
@pytest.mark.parametrize("y", [10, 20])
def test_combination(x, y):
# 会运行 3 * 2 = 6 次测试
assert (x + y) > 0
运行结果:
test_example.py::test_combination[1-10] PASSED
test_example.py::test_combination[1-20] PASSED
test_example.py::test_combination[2-10] PASSED
test_example.py::test_combination[2-20] PASSED
test_example.py::test_combination[3-10] PASSED
test_example.py::test_combination[3-20] PASSED
3.4.4 参数化与标记结合
import pytest
@pytest.mark.parametrize("a, b, expected", [
(1, 1, 2),
(2, 2, 4),
pytest.param(3, 3, 6, marks=pytest.mark.slow),
pytest.param(4, 4, 8, marks=[pytest.mark.slow, pytest.mark.integration]),
])
def test_with_marks(a, b, expected):
assert a + b == expected
3.5 Fixture 插件
3.5.1 什么是 Fixture
Fixture 是 pytest 中用于提供测试数据和测试环境的功能。关于 Fixture 的详细内容,请参考 07.pytest的fixture.md 文档。
3.5.2 内置 Fixture
1. tmp_path:临时目录
def test_create_file(tmp_path):
# tmp_path 是一个 Path 对象,指向临时目录
file_path = tmp_path / "test.txt"
file_path.write_text("Hello, pytest!")
assert file_path.read_text() == "Hello, pytest!"
assert file_path.exists()
2. tmpdir:临时目录(旧版本,推荐使用 tmp_path)
def test_create_file_old(tmpdir):
# tmpdir 是一个 py.path.local 对象
file_path = tmpdir.join("test.txt")
file_path.write("Hello, pytest!")
assert file_path.read() == "Hello, pytest!"
3. capsys:捕获标准输出
def test_print_output(capsys):
print("Hello, World!")
captured = capsys.readouterr()
assert captured.out == "Hello, World!n"
4. capfd:捕获文件描述符输出
def test_stderr_output(capfd):
import sys
sys.stderr.write("Error message")
captured = capfd.readouterr()
assert captured.err == "Error message"
5. monkeypatch:临时修改环境
def test_env_variable(monkeypatch):
# 设置环境变量
monkeypatch.setenv("MY_VAR", "test_value")
import os
assert os.environ["MY_VAR"] == "test_value"
# 测试结束后,环境变量会自动恢复
def test_sys_path(monkeypatch):
import sys
original_path = sys.path.copy()
# 修改 sys.path
monkeypatch.syspath_prepend("/custom/path")
# 测试结束后,sys.path 会自动恢复
6. request:访问测试请求信息
def test_request_info(request):
# 获取测试函数名
print(f"测试函数名: {request.function.__name__}")
# 获取测试文件路径
print(f"测试文件: {request.fspath}")
# 获取标记
print(f"标记: {request.node.get_closest_marker('slow')}")
4. 常用第三方插件
4.1 pytest-html:生成 HTML 报告
4.1.1 安装
pip install pytest-html
4.1.2 基本用法
# 生成 HTML 报告
pytest --html=report.html
# 生成 HTML 报告并包含 CSS 样式(自包含)
pytest --html=report.html --self-contained-html
4.1.3 详细示例
测试文件:test_example.py
def test_addition():
assert 1 + 1 == 2
def test_subtraction():
assert 3 - 1 == 2
def test_failure():
assert 1 == 2 # 这个会失败
运行测试:
pytest --html=report.html --self-contained-html
生成的 HTML 报告包含:
- 测试摘要(通过、失败、跳过数量)
- 详细的测试结果
- 失败测试的错误信息
- 测试执行时间
- 可以通过浏览器打开查看
4.1.4 配置选项
pytest.ini 配置:
[pytest]
addopts = --html=report.html --self-contained-html
自定义报告标题:
pytest --html=report.html --html-title="我的测试报告"
4.2 pytest-cov:代码覆盖率
4.2.1 安装
pip install pytest-cov
4.2.2 基本用法
# 测量覆盖率
pytest --cov=myproject
# 生成 HTML 覆盖率报告
pytest --cov=myproject --cov-report=html
# 生成终端报告
pytest --cov=myproject --cov-report=term
# 生成 XML 报告(用于 CI/CD)
pytest --cov=myproject --cov-report=xml
4.2.3 详细示例
项目结构:
myproject/
├── myproject/
│ ├── __init__.py
│ ├── math.py
│ └── string.py
└── tests/
├── test_math.py
└── test_string.py
myproject/math.py:
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
raise ValueError("除数不能为0")
return a / b
tests/test_math.py:
from myproject.math import add, subtract
def test_add():
assert add(1, 2) == 3
def test_subtract():
assert subtract(5, 2) == 3
运行覆盖率测试:
pytest --cov=myproject --cov-report=html --cov-report=term
输出示例:
---------- coverage: platform win32, python 3.9.0 -----------
Name Stmts Miss Cover
----------------------------------------
myproject/__init__.py 0 0 100%
myproject/math.py 8 4 50%
myproject/string.py 0 0 100%
----------------------------------------
TOTAL 8 4 50%
HTML 报告会显示:
- 哪些行被测试覆盖(绿色)
- 哪些行没有被覆盖(红色)
- 覆盖率百分比
4.2.4 配置选项
pytest.ini 配置:
[pytest]
addopts =
--cov=myproject
--cov-report=html
--cov-report=term
--cov-branch # 包含分支覆盖率
--cov-fail-under=80 # 覆盖率低于 80% 时失败
.coveragerc 配置文件:
[run]
source = myproject
omit =
*/tests/*
*/test_*.py
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
4.3 pytest-xdist:并行执行测试
4.3.1 安装
pip install pytest-xdist
4.3.2 基本用法
# 使用所有 CPU 核心并行运行
pytest -n auto
# 指定使用 4 个进程
pytest -n 4
# 使用所有 CPU 核心(等同于 auto)
pytest -n auto
4.3.3 详细示例
测试文件:test_slow.py
import time
def test_slow_1():
time.sleep(1)
assert True
def test_slow_2():
time.sleep(1)
assert True
def test_slow_3():
time.sleep(1)
assert True
def test_slow_4():
time.sleep(1)
assert True
串行运行(不使用 xdist):
pytest test_slow.py -v
# 执行时间:约 4 秒(1秒 * 4个测试)
并行运行(使用 xdist):
pytest test_slow.py -n 4 -v
# 执行时间:约 1 秒(4个测试同时运行)
4.3.4 高级用法
1. 按测试文件分组并行
# 每个测试文件在一个进程中运行
pytest --dist=loadfile -n 4
2. 按测试类分组并行
# 每个测试类在一个进程中运行
pytest --dist=loadscope -n 4
3. 循环失败模式
# 只重新运行失败的测试
pytest --looponfail -n 4
4.3.5 注意事项
- Fixture 作用域:并行运行时,
session和module作用域的 fixture 会在每个进程中独立创建 - 共享资源:避免多个进程同时访问同一个文件或数据库
- 随机性:测试执行顺序可能不同
4.4 pytest-timeout:测试超时控制
4.4.1 安装
pip install pytest-timeout
4.4.2 基本用法
# 设置全局超时时间为 10 秒
pytest --timeout=10
# 设置超时时间为 5 秒,使用信号方式(推荐)
pytest --timeout=5 --timeout-method=thread
4.4.3 详细示例
示例 1:全局超时
import time
def test_slow_operation():
time.sleep(15) # 如果超时时间设置为 10 秒,这个测试会失败
assert True
运行:
pytest --timeout=10 test_example.py
输出:
test_example.py::test_slow_operation TIMEOUT [10.00s]
示例 2:单个测试超时
import pytest
import time
@pytest.mark.timeout(5) # 这个测试最多运行 5 秒
def test_with_timeout():
time.sleep(10) # 会超时
assert True
@pytest.mark.timeout(10) # 这个测试最多运行 10 秒
def test_with_longer_timeout():
time.sleep(5) # 不会超时
assert True
示例 3:使用函数装饰器
import pytest
import time
@pytest.mark.timeout(3, method='thread')
def test_thread_timeout():
time.sleep(5)
assert True
4.4.4 配置选项
pytest.ini 配置:
[pytest]
addopts = --timeout=10 --timeout-method=thread
超时方法:
thread:使用线程(推荐,跨平台)signal:使用信号(仅 Unix 系统)
4.5 pytest-rerunfailures:失败重试
4.5.1 安装
pip install pytest-rerunfailures
4.5.2 基本用法
# 失败后重试 3 次
pytest --reruns 3
# 失败后重试 3 次,每次重试间隔 2 秒
pytest --reruns 3 --reruns-delay 2
4.5.3 详细示例
示例 1:全局重试
import random
def test_flaky_test():
# 这个测试有时会失败(模拟不稳定的测试)
result = random.choice([True, False])
assert result, "随机失败"
运行:
pytest --reruns 3 test_example.py -v
输出:
test_example.py::test_flaky_test RERUN
test_example.py::test_flaky_test RERUN
test_example.py::test_flaky_test PASSED
示例 2:单个测试重试
import pytest
import random
@pytest.mark.flaky(reruns=5, reruns_delay=1)
def test_specific_retry():
result = random.choice([True, False])
assert result
示例 3:条件重试
import pytest
@pytest.mark.flaky(reruns=3, condition=True)
def test_conditional_retry():
# 只有在 condition=True 时才会重试
assert False
4.5.4 配置选项
pytest.ini 配置:
[pytest]
addopts = --reruns 3 --reruns-delay 2
4.6 pytest-mock:Mock 功能增强
4.6.1 安装
pip install pytest-mock
4.6.2 基本用法
pytest-mock 提供了一个 mocker fixture,它是对 unittest.mock 的封装,使用更方便。
4.6.3 详细示例
示例 1:Mock 函数
def test_mock_function(mocker):
# Mock 一个函数
mock_func = mocker.patch('module.function')
mock_func.return_value = 42
# 调用被 Mock 的函数
result = module.function()
assert result == 42
mock_func.assert_called_once()
示例 2:Mock 对象方法
class Calculator:
def add(self, a, b):
return a + b
def test_mock_method(mocker):
calc = Calculator()
# Mock add 方法
mocker.patch.object(calc, 'add', return_value=100)
result = calc.add(1, 2)
assert result == 100
示例 3:Mock 环境变量
def test_mock_env(mocker):
mocker.patch.dict('os.environ', {'MY_VAR': 'test_value'})
import os
assert os.environ['MY_VAR'] == 'test_value'
示例 4:Mock HTTP 请求
import requests
def test_mock_http(mocker):
# Mock requests.get
mock_get = mocker.patch('requests.get')
mock_get.return_value.json.return_value = {'status': 'ok'}
mock_get.return_value.status_code = 200
response = requests.get('http://example.com/api')
assert response.json() == {'status': 'ok'}
assert response.status_code == 200
4.7 pytest-asyncio:异步测试支持
4.7.1 安装
pip install pytest-asyncio
4.7.2 基本用法
import pytest
import asyncio
@pytest.mark.asyncio
async def test_async_function():
await asyncio.sleep(0.1)
assert True
@pytest.mark.asyncio
async def test_async_http():
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get('http://httpbin.org/get') as response:
assert response.status == 200
4.7.3 详细示例
示例 1:测试异步函数
import pytest
import asyncio
async def fetch_data():
await asyncio.sleep(0.1)
return {"data": "test"}
@pytest.mark.asyncio
async def test_fetch_data():
result = await fetch_data()
assert result == {"data": "test"}
示例 2:异步 Fixture
import pytest
@pytest.fixture
async def async_fixture():
# 异步前置操作
data = await setup_async_data()
yield data
# 异步后置清理
await cleanup_async_data(data)
@pytest.mark.asyncio
async def test_with_async_fixture(async_fixture):
assert async_fixture is not None
示例 3:配置异步模式
pytest.ini 配置:
[pytest]
asyncio_mode = auto
4.8 pytest-json-report:JSON 报告
4.8.1 安装
pip install pytest-json-report
4.8.2 基本用法
# 生成 JSON 报告
pytest --json-report --json-report-file=report.json
4.8.3 详细示例
运行测试:
pytest --json-report --json-report-file=report.json -v
生成的 JSON 报告示例:
{
"created": 1234567890.123,
"duration": 1.234,
"exitcode": 0,
"root": "/path/to/project",
"environment": {
"Python": "3.9.0",
"Platform": "Windows-10"
},
"summary": {
"passed": 10,
"failed": 2,
"skipped": 1,
"total": 13
},
"tests": [
{
"nodeid": "test_example.py::test_addition",
"outcome": "passed",
"duration": 0.001,
"setup": {
"duration": 0.0005,
"outcome": "passed"
},
"call": {
"duration": 0.001,
"outcome": "passed"
}
}
]
}
4.9 pytest-sugar:美化输出
4.9.1 安装
pip install pytest-sugar
4.9.2 功能
pytest-sugar 会自动美化 pytest 的输出,显示:
- 彩色输出
- 进度条
- 即时显示失败信息(不需要等待所有测试完成)
4.9.3 使用
安装后自动生效,无需额外配置:
pytest -v
输出对比:
不使用 pytest-sugar:
test_example.py ... [100%]
使用 pytest-sugar:
test_example.py ✓✓✓ [100%]
4.10 pytest-benchmark:性能测试
4.10.1 安装
pip install pytest-benchmark
4.10.2 基本用法
def test_performance(benchmark):
result = benchmark(lambda: sum(range(1000)))
assert result == 499500
4.10.3 详细示例
示例 1:基准测试函数
def test_list_comprehension(benchmark):
result = benchmark(lambda: [x * 2 for x in range(1000)])
assert len(result) == 1000
def test_map_function(benchmark):
result = benchmark(lambda: list(map(lambda x: x * 2, range(1000))))
assert len(result) == 1000
运行:
pytest test_benchmark.py --benchmark-only
输出:
-------------------------------- benchmark: 2 tests --------------------------------
Name (time in us) Min Max Mean StdDev
----------------------------------------------------------------------------------------
test_list_comprehension 45.23 67.89 52.34 3.21
test_map_function 78.45 102.34 89.12 5.67
----------------------------------------------------------------------------------------
示例 2:比较不同实现
def test_algorithm_v1(benchmark):
def algorithm():
return sum(range(1000))
benchmark(algorithm)
def test_algorithm_v2(benchmark):
def algorithm():
total = 0
for i in range(1000):
total += i
return total
benchmark(algorithm)
5. 插件组合使用
5.1 常用组合配置
在实际项目中,通常会同时使用多个插件。以下是一些常见的组合:
5.1.1 基础测试配置
pytest.ini:
[pytest]
addopts =
-v
--html=report.html
--self-contained-html
--cov=myproject
--cov-report=html
--cov-report=term
--cov-fail-under=80
markers =
slow: 慢速测试
integration: 集成测试
smoke: 冒烟测试
5.1.2 CI/CD 配置
pytest.ini:
[pytest]
addopts =
-v
--cov=myproject
--cov-report=xml
--cov-report=term
--junitxml=junit.xml
--html=report.html
--self-contained-html
-n auto
--reruns 2
--reruns-delay 1
5.1.3 开发环境配置
pytest.ini:
[pytest]
addopts =
-v
--tb=short
--cov=myproject
--cov-report=term-missing
-n auto
--looponfail
markers =
slow: 慢速测试(跳过)
运行慢速测试:
# 默认跳过慢速测试
pytest
# 显式运行慢速测试
pytest -m slow
5.2 完整示例项目
项目结构:
myproject/
├── myproject/
│ ├── __init__.py
│ ├── calculator.py
│ └── utils.py
├── tests/
│ ├── __init__.py
│ ├── test_calculator.py
│ └── test_utils.py
├── pytest.ini
├── requirements.txt
└── README.md
requirements.txt:
pytest>=7.0.0
pytest-html>=3.1.0
pytest-cov>=4.0.0
pytest-xdist>=3.0.0
pytest-timeout>=2.1.0
pytest-rerunfailures>=11.0.0
pytest-sugar>=0.9.6
pytest.ini:
[pytest]
addopts =
-v
--html=reports/report.html
--self-contained-html
--cov=myproject
--cov-report=html:reports/coverage
--cov-report=term
--cov-branch
--cov-fail-under=80
-n auto
--reruns 2
--reruns-delay 1
--timeout=30
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
markers =
slow: 标记为慢速测试
integration: 标记为集成测试
smoke: 标记为冒烟测试
unit: 标记为单元测试
myproject/calculator.py:
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError("除数不能为0")
return a / b
tests/test_calculator.py:
import pytest
from myproject.calculator import Calculator
@pytest.fixture
def calc():
return Calculator()
@pytest.mark.unit
@pytest.mark.parametrize("a, b, expected", [
(1, 2, 3),
(5, 3, 8),
(10, 20, 30),
])
def test_add(calc, a, b, expected):
assert calc.add(a, b) == expected
@pytest.mark.unit
def test_subtract(calc):
assert calc.subtract(5, 2) == 3
@pytest.mark.unit
def test_multiply(calc):
assert calc.multiply(3, 4) == 12
@pytest.mark.unit
def test_divide(calc):
assert calc.divide(10, 2) == 5
@pytest.mark.unit
def test_divide_by_zero(calc):
with pytest.raises(ValueError):
calc.divide(10, 0)
@pytest.mark.slow
def test_slow_operation(calc):
import time
time.sleep(2)
assert calc.add(1, 1) == 2
运行测试:
# 运行所有测试
pytest
# 只运行单元测试
pytest -m unit
# 跳过慢速测试
pytest -m "not slow"
# 生成报告
pytest --html=reports/report.html --cov=myproject