📌 项目地址:pytest-dev/pytest | ⭐ 13,989 颗星 | 🔧 Python | 📜 未标注
为什么需要pytest
Python开发者都知道,写测试是保证代码质量的关键。但传统的unittest要求你写一堆样板代码:定义类、继承TestCase、用self.assertEqual等方法。如果测试逻辑复杂,这些额外结构反而分散了精力。
pytest的出现改变了这一切。它的核心理念是:用最少的代码写最直的测试。你只需要写普通的函数,用assert做断言,然后运行pytest。小到单文件脚本,大到几千个测试用例的项目,它都能胜任。
快速上手:两条命令写出第一个测试
根据官方README,安装pytest只需一条命令(推荐使用虚拟环境):
pip install -U pytest
新建一个测试文件test_sample.py,写入一个函数:
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
然后在终端运行:
pytest
你会看到清晰的输出,指出哪一行断言失败、实际值和预期值是什么。整个过程不需要继承任何类,不需要调用任何测试运行器。
这就是pytest最直观的魅力:用Python原生的assert,加上自动发现所有test_开头的函数。
核心特性:能平替unittest的几大杀器
1. Fixture——替代setUp/tearDown的更优雅方案
在unittest中,每个测试类都需要写setUp和tearDown方法,如果多个测试类共享资源,还得用setUpClass或setUpModule。pytest的fixture机制则灵活得多:
import pytest
@pytest.fixture
def input_value():
return 39
def test_divisible_by_3(input_value):
assert input_value % 3 == 0
fixture可以自动注入到测试函数中,作用范围通过scope参数控制(function/class/module/session),可以返回任何Python对象,还可以互相依赖。
2. 参数化测试——消除重复代码
当需要测试多组输入数据时,unittest要么写多个测试方法,要么用subTest。pytest提供了@pytest.mark.parametrize装饰器:
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
("6*9", 42), # 这个会失败,展示错误
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
一行装饰器就能生成多个测试用例,每个失败独立显示。
3. 丰富的插件生态
pytest有500+插件,官方文档列出了分类。例如:
– pytest-cov:集成覆盖率统计
– pytest-xdist:并行执行测试
– pytest-mock:无缝使用Mock对象
– pytest-django、pytest-flask:框架专用插件
插件通过pip install安装后自动启用,无需额外配置(大多数情况下)。
4. 自动发现与运行控制
默认从当前目录开始递归查找test_*.py或*_test.py文件,并运行其中的test_*函数和Test*类中的方法。也可以通过-k选择测试:
pytest -k "not slow" # 跳过标记为slow的测试
pytest -x # 首次失败后停止
pytest --lf # 只运行上次失败的测试
与unittest的区别:为什么你该考虑切换
| 维度 | unittest | pytest |
|---|---|---|
| 编写方式 | 需继承TestCase,使用assertEqual等方法 |
纯函数+assert语句 |
| 测试发现 | 需手动调用unittest.main(),默认只发现Test*类 |
自动递归发现 |
| 资源管理 | setUp/tearDown,作用域固定 |
fixture,可自定义作用域和依赖 |
| 参数化 | 不支持原生,需借用subTest或第三方库 |
内置@pytest.mark.parametrize |
| 插件系统 | 有但不够丰富 | 数百个官方/社区插件 |
| 错误报告 | 抛出异常,信息简略 | 彩色输出,显示具体变量值 |
对于已有unittest代码,pytest提供了兼容模式:可以直接运行unittest.TestCase派生的测试,并享受pytest的插件和输出格式。也就是零迁移成本。
注意事项与局限性
- Python版本要求:pytest 7.x 需要 Python 3.7+,pytest 8.x 需要 Python 3.8+(根据README中setup.py的python_requires)。如果你的项目还在用Python 2.7或3.6,则无法使用最新版。
- 隐式fixture依赖:fixture通过参数名称自动注入,如果参数名拼写错误,pytest会报告缺少 fixture,这个错误提示比较明确,但刚上手时可能不习惯。
- 插件冲突:少数插件之间可能存在兼容性问题,建议在项目requirements.txt中固定pytest版本。
- 大规模测试的性能:相比unittest,pytest自动发现和fixture构建有一定性能开销。但对于大多数项目(数千测试以内)完全无感。如果超过万级别,可以用
pytest-xdist并行或--co缓存fixture结果。 - 非标准测试结构:pytest默认发现规则是基于文件/函数/类命名模式的。如果你的测试文件放在不合规的目录里,需要添加
conftest.py或通过--ignore排除。
适宜人群与项目类型
pytest适合任何需要写Python测试的项目:从个人小工具到大型多模块系统、从纯Python库到Django/Flask Web应用、从数据科学脚本到机器学习模型评估。它不挑项目,因为它只负责“运行测试”这件事,而测试内容(数据库、网络、UI等)由其他库或插件处理。
如果你正在维护一个长期项目,以往惯用unittest,那么切换为pytest可以显著减少样板代码、提升可读性,并享受丰富的第三方插件。即使不切换,pytest也能作为运行器直接执行你的unittest用例。