Python 用 pytest 做测试

pytest 用断言即测试、fixture 与参数化组织可维护测试套件。

#type / concept #status / growing #tech / dev #resource / python #tech / lang / python

[!info] 关联笔记

Python 用 pytest 做测试

这个概念为什么出现

没有自动化测试,重构与依赖升级都在赌。pytest 以低样板成为 Python 事实标准:普通 assert、丰富插件、fixture 注入。

[!abstract] 一句话理解 pytest 收集 test_*,用 assert 验证行为;fixture 管理依赖,参数化覆盖多例。

最小可运行示例

先把示例放进业务场景,再看代码:

场景:为折扣函数写参数化测试

# test_discount_pytest_style.py
# 业务意图:验证折扣计算(可直接 pytest 本文件)。
# 教学点:
# - assert;
# - pytest.mark.parametrize;
# - 异常断言。

import pytest


def price_after_discount(price_cents: int, discount: float) -> int:
    if not 0 <= discount <= 1:
        raise ValueError("bad discount")
    return int(price_cents * (1 - discount))


@pytest.mark.parametrize(
    "price,discount,expected",
    [(1000, 0.1, 900), (1000, 0.0, 1000)],
)
def test_price_after_discount(price, discount, expected):
    assert price_after_discount(price, discount) == expected


def test_bad_discount():
    with pytest.raises(ValueError):
        price_after_discount(1000, 2)

建议运行:

pytest -q test_discount_pytest_style.py

期望输出:

...
3 passed

(若环境未装 pytest:pip install pytest 后重跑。)

结合场景再看三个关注点

  1. 表驱动减少重复测试函数。
  2. raises 锁定失败契约。
  3. 业务函数与测试可同逻辑演进。

Fixture、Fake 与 monkeypatch 怎么配合

这三个词经常一起出现,但职责不同。

[!abstract] 先理解设计,再记 API

  • fixture:准备测试需要的对象或环境
  • Fake:用一个简化实现替代昂贵、不稳定或不想一起测试的真实依赖
  • monkeypatch:在测试期间临时把真实对象替换成 Fake / Stub

例如只想测试 FastAPI route,而不想真的运行 DiagnosisService 和 LLM:

测试目标

FastAPI Route

Fake DiagnosisService

真实 DiagnosisService / LLM 不进入这条测试路径

pytest 可以这样完成替换:

class FakeDiagnosisService:
    async def generate_diagnosis(self, **kwargs):
        return {"diagnoses": []}


def test_route(client, monkeypatch):
    fake = FakeDiagnosisService()

    monkeypatch.setattr(
        "src.api.routes.diagnosis.get_diagnosis_service",
        lambda: fake,
    )

    response = client.post(
        "/api/diagnosis/analyze",
        json={"extracted_info": [], "profile": {}},
    )

    assert response.status_code == 200

这里真正重要的不是 monkeypatch.setattr 语法,而是测试边界:

我只验证 Route 的 HTTP 行为

所以把更深层的真实依赖隔离掉

monkeypatch 是工具,Fake 是测试替身;它们都不是 Unit / Integration / E2E 之外的新测试层级。完整模型见 测试层级与测试边界

核心概念与准确模型

  • 发现规则:test_*.py / Test* / test_*
  • fixture 作用域:function/module/session
  • conftest.py 共享
  • 插件:cov、xdist、mock

边界情况与反直觉行为

  1. 断言重写让失败信息更详,但奇妙魔法需了解。
  2. 测试间污染全局状态。
  3. 异步测试需插件/模式。

常见误区

[!warning] 常见误区:只测成功路径 错误理解:happy path 足够。
正确模型:边界与异常同样是契约。

工程实践

  • Arrange-Act-Assert
  • 命名表达场景
  • CI 默认 pytest

本节总结

pytest 让测试成为日常反馈环。先测行为,再谈覆盖率数字。

自测题

  1. fixture 解决什么?
  2. 参数化适合什么?
  3. Fake 和 monkeypatch 的职责有什么不同?
参考答案
  1. 可复用的测试依赖准备/清理。
  2. 同一逻辑多组输入输出。
  3. Fake 是替代真实依赖的简化实现;monkeypatch 是测试期间完成临时替换的工具。

延伸阅读与资料来源

资料类型支撑内容
pytest docs文档官方
创建于 2026/7/15 更新于 2026/8/14