Python 用 pytest 写测试
AI 辅助生成本文由 AI 起草并经人工核查,示例均标明软件版本;发现错误请以官方文档为准。
测试不是为了凑覆盖率,而是让你改代码时敢于下手。这一篇用 pytest 从零搭起可运行的测试。
环境准备
python -m venv .venv
source .venv/bin/activate
python -m pip install pytest==8.3.4
pytest --version
示例软件版本:Python 3.13 + pytest 8.3。
第一个测试
被测代码 calc.py:
def add(a: int, b: int) -> int:
return a + b
def divide(a: int, b: int) -> float:
if b == 0:
raise ValueError("除数不能为 0")
return a / b
测试 test_calc.py:
from calc import add, divide
def test_add():
assert add(1, 2) == 3
运行:
pytest
输出要点:
collected 1 item
test_calc.py . [100%]
1 passed
pytest 的规则:
- 测试文件:
test_*.py或*_test.py - 测试函数:以
test_开头 - 断言用裸
assert,不需要self.assertEqual
断言
def test_list():
items = [1, 2, 3]
assert len(items) == 3
assert 2 in items
assert items == [1, 2, 3]
失败时 pytest 会打印实际值,定位很快:
E assert 4 == 3
E + where 4 = len([1, 2, 3, 4])
不要写 assert x == True、assert x is True,直接 assert x。
测试异常
import pytest
def test_divide_by_zero():
with pytest.raises(ValueError, match="除数不能为 0"):
divide(1, 0)
match 用正则检查异常消息。拿到异常对象细看:
with pytest.raises(ValueError) as exc:
divide(1, 0)
assert "除数" in str(exc.value)
参数化
同一逻辑用多组输入跑,避免复制粘贴:
@pytest.mark.parametrize(
("a", "b", "expected"),
[
(1, 2, 3),
(0, 0, 0),
(-1, 1, 0),
],
)
def test_add_cases(a, b, expected):
assert add(a, b) == expected
组合多个参数化装饰器可以做笛卡尔积,覆盖更多组合。
fixture:准备与清理
import pytest
@pytest.fixture
def sample_file(tmp_path):
path = tmp_path / "input.csv"
path.write_text("name,age\n小明,18\n", encoding="utf-8")
return path
def test_read(sample_file):
assert sample_file.exists()
- fixture 用
@pytest.fixture定义,测试函数按参数名注入。 tmp_path是内置 fixture:每个测试一个临时目录,自动清理。- 需要一次性准备(如数据库连接)用
scope="module"或scope="session"。
组织测试
myapp/
├── myapp/
│ ├── __init__.py
│ └── calc.py
└── tests/
├── conftest.py # 共享 fixture
├── test_calc.py
└── test_cli.py
常用命令:
pytest # 全部
pytest tests/test_calc.py # 指定文件
pytest -k "add" # 按名字匹配
pytest -v # 显示每个用例名
pytest -x # 第一次失败就停
pytest --tb=short # 精简栈
覆盖率(可选):
python -m pip install pytest-cov
pytest --cov=myapp --cov-report=term-missing
测试什么、不测试什么
值得测:
- 业务逻辑与边界:空输入、0、负数、超长字符串。
- 曾经出过 bug 的地方(回归测试)。
- 对外接口的行为,而不是内部实现细节。
不值得测:
- 第三方库本身。
- 一行 getter/setter。
- 依赖网络、当前时间、随机数的代码——要用 mock 或注入,而不是真连。
常见错误与注意事项
1. 测试文件名不符合约定
calc_test.py 可以,calc.py 不会被收集(pytest 只收集 test_*.py / *_test.py)。
2. 测试之间互相依赖
pytest 不保证执行顺序(可用 pytest-randomly 强制打乱来暴露问题)。每个测试自己准备数据。
3. 测试有副作用
写真实文件到项目目录、连真实数据库、发真实请求。用 tmp_path、fixture 和 mock 隔离。
4. 断言过于宽松
assert result # 只要非空就过,什么也证明不了
断言具体值或具体属性。
5. 把测试当成调试脚本
测试要能反复跑、结果稳定。留着 print 调试输出用 pytest -s 临时查看,不要留在提交里。
小结
- 文件
test_*.py、函数test_,用裸assert。 - 异常用
pytest.raises,多组输入用parametrize。 - fixture 负责准备与清理,
tmp_path处理临时文件。 - 测行为不测实现,隔离外部依赖,保证测试可重复。