SuperAPI AI 工具集

python-tdd

测试与质量

查看项目

更新于 10-15 09:51

技能介绍

Python Testing Patterns 是一个面向 Python 的测试编写技能,主题是生成聚焦行为、而非耦合实现细节的 pytest 测试套件。它适用于编写 Python 测试、提升覆盖率、审查测试质量或实践 TDD 的场景。

一、测试哲学 该技能明确了测试的取舍。应当测试的内容包括:可观察行为与返回值、面向用户的工作流与 API 契约、系统边界处的边界情况与错误处理、状态转移与副作用、数据校验与转换规则。不应测试的内容包括:框架内部机制(pytest、SQLAlchemy、FastAPI 的机制)、实现细节(私有方法、内部变量名)、第三方库行为、没有分支逻辑的简单工具函数,以及“对象为真值”这类没有实际断言价值的检查。

Mock 策略上:应当 mock 外部 API 调用与 HTTP 请求、单元测试中的数据库查询、文件系统操作、依赖时间的行为(datetime.now、time.time)、环境变量;不应 mock 自己的模型与 dataclass、简单工具函数、被测函数本身,以及所依赖的框架特性。

测试数量纪律:简单模块每个文件最多 10 个测试,复杂模块最多 15 个;超过则说明模块过于复杂,应建议拆分。

二、TDD 工作流 采用红-绿-重构循环:RED 阶段写一个失败的测试,且必须因正确的原因失败(不是导入错误、不是语法错误);GREEN 阶段写最小可用代码使其通过,不多写;REFACTOR 阶段在所有测试保持绿色的前提下改进代码,不引入新行为。

覆盖率目标按优先级划分:P0 关键路径(认证、支付、数据完整性)要求 100% 行与分支覆盖;P1 核心业务逻辑与公开 API 要求 90% 以上行覆盖;P2 工具、辅助函数、配置要求 80% 以上行覆盖。整体目标为 80% 以上行覆盖,关键路径 100%。

三、命名约定(BDD) 支持两种组织方式。类分组方式按功能再按场景嵌套,例如 TestUserService 下单层 TestCreateUser,方法名为 test_should_create_when_valid_data、test_should_raise_when_email_exists。扁平函数命名格式为 test_<action>should<outcome>when<condition>,例如 test_create_user_should_succeed_when_valid_data、test_login_should_fail_when_password_expired。

四、AAA 结构 测试按 Arrange-Act-Assert 三段组织。示例:Arrange 阶段用 make_account 构造余额为 100 的 sender 与余额为 50 的 receiver;Act 阶段调用 transfer(sender, receiver, amount=30);Assert 阶段断言 sender.balance == 70、receiver.balance == 80。

五、通过工厂夹具提供可复用伪对象 优先在 conftest.py 中使用工厂夹具,而不是裸字典。例如定义 make_user 夹具,内部函数 _make_user 提供 name、email、active 默认值并返回 User 实例;测试中 user = make_user(active=True),调用 user.deactivate() 后断言 user.active is False。

六、核心模式 夹具应使用尽可能窄的作用域。例如 db_session 夹具创建 SessionLocal,yield 后在收尾中 rollback 并 close;api_client 可用 scope="module" 或对引擎用 scope="session",返回 TestClient(app)。夹具通过 conftest.py 共享,pytest 会自动发现。

参数化应带自定义 ID。例如用 pytest.mark.parametrize 列出输入与期望值,并通过 pytest.param(..., id="valid-email")、id="missing-at"、id="empty-string" 标注每个用例。

Mocking 只需掌握少数模式。使用 @patch("myapp.services.requests.get") 打补丁到“使用处”(顶层导入),设置 mock_get.return_value.json.return_value 并断言 mock_get.assert_called_once_with 具体 URL;使用 @patch("myapp.services.UserRepository", autospec=True) 让 autospec 捕获签名漂移;使用 side_effect=ConnectionError("timeout") 模拟网络错误,并用 pytest.raises 断言抛出 ServiceUnavailableError。

惰性导入的坑:当函数在另一个函数内部导入时,它在使用处并不是模块级属性,因此应在定义处打补丁,而不是使用处。错误做法是 monkeypatch.setattr("myapp.services.processor.get_db", mock_db)(会抛 AttributeError),正确做法是 monkeypatch.setattr("myapp.database.get_db", mock_db)。规则是:若 monkeypatch.setattr 抛出 AttributeError,检查目标是否为惰性导入,若是则改为在定义该函数的模块上打补丁。

异步测试使用 @pytest.mark.asyncio 装饰 async 测试函数;在 pyproject.toml 中设置 asyncio_mode = "auto" 可省略该装饰器。

属性测试使用 hypothesis 的 @given 与 strategies as st,例如 @given(st.integers(min_value=0, max_value=1000)) 验证 deposit 后余额等于存入金额。

临时文件与环境变量:使用 tmp_path 自动清理,例如导出报告到 tmp_path / "report.csv" 并断言内容包含预期值;使用 monkeypatch.setenv("DATABASE_URL", "postgresql://localhost/test") 并断言 get_database_url() 返回该值。

七、反模式 该技能列出多项反模式及替代做法。仅断言 obj is not None 没有行为价值,应改为断言具体属性或返回值;mock 被测函数本身等于什么都没测,应改为 mock 其依赖;简单模块写 40 个以上测试是过度测试或模块臃肿,应拆分模块或合并参数化用例;测试框架内部机制是在验证 pytest/SQLAlchemy 而非自己的代码,应通过公开 API 测试自身逻辑;在每个测试里复制粘贴 mock 设置脆弱难维护,应抽取为夹具或工厂函数;直接测试私有方法会让测试耦合实现,应通过公开接口测试;在测试代码里捕获异常会静默吞掉真实失败,应使用 pytest.raises 上下文管理器;测试体内没有断言则永远通过、证明不了任何东西,每个测试都必须断言;仅断言 mock.called 无法验证参数,应使用 assert_called_once_with(expected_args);硬编码黄金值(如 == 660)会在算法改进时误报,应断言不变量、使用 pytest.approx、从输入推导;在子目录 conftest 中做重量级 mock 会因根测试先加载真实依赖而太晚,应把全部重量级依赖 mock 放在根 tests/conftest.py;重量级依赖测试缺少标记会在依赖被默认 mock 后静默失败,应标记 @pytest.mark.slow 并使用 --strict-markers;外部服务 mock 不完整会让一个未 mock 的服务挂起 CI(例如 google.auth.default()),应在定稿集成 conftest 前审计所有外部调用;在使用处给惰性导入打补丁无效,应在定义处打补丁。

八、pytest 基础设施规则 conftest 执行顺序:根 tests/conftest.py 最先运行;子目录 conftest 只在其测试被收集时运行。重量级 mock 必须放在根 conftest。若 mock ortools、scipy、prometheus_client 等大型原生依赖,需在根 tests/conftest.py 中完成,因为子目录 conftest 的 mock 太晚,收集阶段的导入已加载真实模块。示例做法是遍历模块名列表,若不在 sys.modules 中则赋值为 MagicMock()。

外部服务 mock 完整性:应用使用的每个外部服务都必须在集成 conftest 中有对应 mock,包括数据库连接/会话(CI 中无真实 DB)、认证令牌校验(测试中无认证服务器)、云存储上传/下载(google.auth.default() 会挂起)、邮件发送函数(会发送真实邮件)、支付 charge/refund(会命中真实 API)。审计方式是 grep 生产代码中的外部服务导入,确认每一项都有 mock。

标记纪律:需要真实重量级依赖(scipy.optimize、真实数据库、ML 模型)的测试必须加标记,可用模块级 pytestmark = pytest.mark.slow,或对类/测试加 @pytest.mark.slow。pyproject.toml 中的默认 addopts 为 -m "not slow and not e2e"。

九、参考文档 该技能附带三份参考:references/tdd-best-practices.md 覆盖 TDD 纪律、红-绿-重构深度与覆盖率策略;references/framework-config.md 覆盖 pytest 配置、插件与 CI/CD;references/pytest-infrastructure.md 覆盖 conftest 顺序、重量级依赖 mock、mock 目标决策树与外部服务审计。外部参考包括 pytest 文档 https://docs.pytest.org/、unittest.mock 文档 https://docs.python.org/3/library/unittest.mock.html、hypothesis 文档 https://hypothesis.readthedocs.io/、pytest-asyncio 文档 https://pytest-asyncio.readthedocs.io/。

限制方面:该技能未提供收费、价格或平台可用性信息;其内容聚焦 pytest 生态的测试编写规范。