Pytest 单元测试入门指南
Pytest 是 Python 常用测试框架。本文从安装讲起:编写与运行测试、测试设计原则、用例过滤、参数化、异常断言、fixture 与插件生态,一篇上手。
本文依据官方文档整理,未执行运行验证或性能基准。代码片段展示局部用法,业务函数、数据和环境需按项目补齐;版本与配置以所引文档为准。
直接回答:Pytest 凭借直观的断言语法(直接写 assert)、强大的 fixture 机制和庞大的插件生态,成为 Python 测试中常见的选择——写好普通函数、用 assert 断言,剩下的交给 pytest。
为什么是 Pytest
相比标准库 unittest 的类继承套路,pytest 的哲学是"少仪式":测试就是普通函数,断言就是普通 assert(失败时自动展开详细的值对比),依赖注入交给 fixture,批量场景交给参数化。可减少部分测试组织样板,具体差异取决于项目。
安装与第一条测试
pip install pytest
被测代码 calc.py:
def divide(a, b):
if b == 0:
raise ValueError("除数不能为零")
return a / b
测试 test_calc.py:
from calc import divide
def test_divide():
assert divide(10, 2) == 5
pytest 会自动发现 test_*.py 文件里 test_ 开头的函数并执行。
运行方式
pytest # 全量
pytest test_calc.py # 指定文件
pytest -v # 详细输出
pytest -x # 首个失败即停
pytest --lf # 只跑上次失败的
设计好测试的三条原则
- 一条用例一个关注点:失败时标题即结论;
- AAA 结构:Arrange(准备)→ Act(执行)→ Assert(断言),段落分明;
- 边界优先:正常值之外,空值、零、极大值、非法输入才是 bug 高发区。
过滤测试
pytest -k "divide" # 按名字模糊匹配
pytest -m slow # 按标记运行(需 @pytest.mark.slow)
pytest test_calc.py::test_divide # 精确到单条
参数化:一组输入一份代码
import pytest
@pytest.mark.parametrize("a,b,want", [
(10, 2, 5),
(9, 3, 3),
(1, 4, 0.25),
])
def test_divide(a, b, want):
assert divide(a, b) == want
用例多了还可以把参数抽成数据类,让每组场景带名字和语义,报告更易读。
断言异常
def test_divide_by_zero():
with pytest.raises(ValueError, match="除数不能为零"):
divide(1, 0)
pytest.raises 同时验证异常类型与消息,比 try/except 断言优雅得多。
fixture 快速上手
@pytest.fixture
def client():
return TestClient(app)
def test_health(client):
assert client.get("/health").status_code == 200
把共享 fixture 放进对应目录的 conftest.py,可供该目录及子目录测试使用(详见 fixtures 专题篇)。
插件生态
pytest-cov:覆盖率;pytest-xdist:多进程并行;pytest-asyncio:异步测试;pytest-mock:Mock 语法糖;pytest-rerunfailures:失败重跑。
插件通常还需要版本匹配、标记或配置;例如pytest-asyncio的使用方式取决于所选模式,这是 pytest 长盛不衰的护城河。
常见问题(FAQ)
Q:pytest 和 unittest 怎么选?
A:新项目直接 pytest:写法更简洁、生态更繁荣。存量 unittest 代码不用重写——pytest 能直接收集运行 unittest 用例,可以渐进迁移。
Q:assert 被 Python 优化模式(-O)去掉怎么办?
A:默认重写的测试模块断言可保留,但普通被导入模块、关闭重写的模块仍可能受 -O 影响。不要以 -O 运行套件来假定所有断言可靠;生产业务校验应显式抛异常。
Q:测试变慢怎么排查?
A:先 -v --durations=10 找出最慢的十条;重资源改长作用域 fixture;最后上 pytest-xdist 并行。
官方参考
资料核对日期:2026-09-29。