pytest 是 Python 生态中最灵活且功能丰富的测试框架之一,支持从简单单元测试到复杂集成测试的各类场景。本文将从安装配置讲起,逐步深入到 Fixture 机制、参数化、标记系统、钩子函数以及常用插件(如并行测试、失败重试、覆盖率),帮助你在实际项目中高效使用 pytest。
- # 安装 pytest 及常用插件
- pip install pytest pytest-html pytest-xdist pytest-rerunfailures pytest-cov
复制代码
推荐的项目结构:将源代码放在 src/,测试放在 tests/,并在根目录放置 pytest.ini 配置文件。
- project_root/
- ├── src/
- │ └── your_module.py
- ├── tests/
- │ ├── conftest.py
- │ ├── test_module_a.py
- │ └── subpackage/
- │ └── test_sub.py
- ├── pytest.ini
- └── requirements.txt
复制代码
pytest 自动发现测试遵循命名规则:测试文件名以 test_ 开头或 _test 结尾,测试类以 Test 开头且不含 __init__,测试函数以 test_ 开头。
- # test_sample.py
- def test_addition():
- assert 1 + 2 == 3
- class TestMathOperations:
- def test_multiplication(self):
- assert 3 * 4 == 12
复制代码
运行测试时通过 pytest 命令指定路径或使用 -k 模糊匹配、-m 按标记选择、-v 详细输出、-x 首次失败停止、--lf 只跑上次失败的用例等。可通过 pytest.ini 配置默认选项:
- [pytest]
- testpaths = tests
- python_files = test_*.py
- python_classes = Test*
- python_functions = test_*
- addopts = -v -s --strict-markers
- markers =
- slow: marks tests as slow (deselect with '-m "not slow"')
- integration: marks tests as integration tests
- log_cli = true
- log_cli_level = INFO
复制代码
pytest 返回退出码:0 全通过,1 有失败,2 中断,3 内部错误,4 参数错误,5 未收集到用例。断言直接使用 Python 原生 assert,失败时提供详细上下文。支持异常断言(pytest.raises)和浮点近似(pytest.approx)。
- def test_complex_assertions():
- assert "hello" == "hello"
- assert 5 > 3
- assert "lo" in "hello"
- import pytest
- with pytest.raises(ValueError, match="invalid literal"):
- int("not_a_number")
- assert 0.1 + 0.2 == pytest.approx(0.3)
复制代码
生成报告:--junitxml=report.xml 用于 CI/CD,--html=report.html 需安装 pytest-html,--cov=src 需安装 pytest-cov 生成覆盖率报告。
Fixture 是 pytest 最强大的特性之一。使用 @pytest.fixture 定义可重用的上下文,通过参数注入到测试函数。支持 yield 实现 setup/teardown:
- @pytest.fixture
- def sample_data():
- return {"name": "Alice", "age": 30}
- def test_user_age(sample_data):
- assert sample_data["age"] == 30
- @pytest.fixture
- def database_connection():
- print("建立数据库连接...")
- conn = {"connected": True, "db": "test_db"}
- yield conn
- print("关闭数据库连接...")
- conn["connected"] = False
复制代码
Fixture 作用域(scope)默认为 function,还可设为 class、module、package、session 以减少重复初始化。参数化 fixtrue 通过 params 和 request.param 为不同测试提供不同数据。设置 autouse=True 自动应用于所有测试。
conftest.py 中定义的 fixtrue 可被同一目录下所有测试共享,pytest 自动发现。
参数化测试使用 @pytest.mark.parametrize 装饰器,可组合多个装饰器产生笛卡尔积,并为用例添加描述性 ID:
- @pytest.mark.parametrize("input_val, expected", [
- pytest.param(1, 2, id="正整数"),
- pytest.param(0, 0, id="零"),
- ])
- def test_double(input_val, expected):
- assert input_val * 2 == expected
复制代码
标记系统包括内置标记(skip、skipif、xfail、parametrize、usefixtures)和自定义标记(需在 pytest.ini 中注册)。可用 -m 选择或排除测试,例如 pytest -m "not slow"。
- @pytest.mark.slow
- def test_slow_operation():
- import time; time.sleep(2)
- assert True
- @pytest.mark.skipif(sys.platform != "linux", reason="仅 Linux")
- def test_linux_specific():
- assert True
复制代码
钩子函数(Hooks)允许在 conftest.py 中自定义 pytest 行为:pytest_configure 添加自定义标记,pytest_collection_modifyitems 重新排序或添加标记,pytest_runtest_makereport 记录失败信息等。
常用插件:
- 并行测试:pytest-xdist,pytest -n auto 使用所有 CPU 核心,--dist=loadscope 按模块分发。
- 失败重试:pytest-rerunfailures,pytest --reruns 3 --reruns-delay 2,或使用 @pytest.mark.flaky。
- 覆盖率:pytest-cov,pytest --cov=src --cov-report=html --cov-report=term-missing --cov-fail-under=80。
- 测试排序:pytest-order,通过 @pytest.mark.order(1) 指定顺序。
pytest 直接兼容 unittest 风格的测试类,可混合运行。
最佳实践:保持测试独立,使用有意义的名称,每个测试只验证一个逻辑概念,合理使用 fixtrue 和 conftest.py,测试正反边界情况。常见问题:测试发现失败时检查命名规则,通过 --collect-only 调试;fixtrue 意外共享时调整 scope 或返回深拷贝;外部依赖使用 unittest.mock 打桩;测试顺序依赖时用 pytest-randomly 随机化以便发现隐藏依赖。
性能优化建议:使用 -n 并行执行,减少 fixtrue 作用域,避免在 fixtrue 中执行耗时操作,使用 pytest-cov 的 --cov-fail-under 确保覆盖率达标。pytest 的灵活性和插件生态使其成为 Python 测试的首选框架,掌握上述技巧能显著提升测试效率与可靠性。 |