关于 python-testing
python-testing 是面向编写或审查 Python 测试的技能,侧重可观察行为、确定性单元测试和模块边界打补丁,也涵盖回归、异步生命周期及 Python 版本相关测试。
主要功能
- 01测试行为与契约
- 02保持单元测试快速且确定
- 03在模块边界打补丁
- 04使用 autospec 和 Fake
- 05覆盖异步清理与取消
- 06处理 Python 版本矩阵
适用场景
- 01审查与实现细节耦合的测试
- 02为缺陷修复添加回归测试
- 03排查不稳定或缓慢的测试
- 04测试异步清理和取消路径
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ahgraber/ skills --skill python-testing安装后在 Claude Code 中输入/python-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/async-and-concurrency-testing.mdfree-threaded-testing.mdmulti-python-testing.mdpytest-practices.mdreliability-lifecycle-testing.mdtesting-strategy.mdscripts/test_pypi_security_audit.pytest_uv_security_audit.py详细介绍
Python 测试
概述
测试可观察行为和契约,而不是内部实现。 保持单元测试快速、确定,并在模块边界处打补丁。
以下是常见场景的首选默认做法。 如果默认做法与项目约束冲突,请提出更合适的替代方案,说明权衡,并指出相应的补偿性控制措施。
有关语言无关的测试原则(AAA 结构、命名、测试替身分类、测试组合策略),请参阅 writing-tests。
本技能在这些基础上扩展了 Python 专属实践。
调用须知
- 调用此技能时,请告知用户技能名称:
python-testing。
适用场景
- 编写或审查单元测试、集成测试或对可靠性敏感的测试。
- 测试不稳定、运行缓慢,或与实现细节耦合。
- 修复 bug 后添加回归测试。
- 测试异步生命周期、取消或清理路径。
- 不确定某项更改需要哪些测试覆盖。
- 跨多个 Python 版本进行测试(nox、CI 矩阵)。
- 验证 free-threaded Python(禁用 GIL 的构建版本)的线程安全性。
不适用场景:
- 纯粹的数据结构或模式验证(请参阅
python-types-contracts)。 - 生产环境中的可观测性或监控问题(请参阅
python-runtime-operations)。 - 测试框架之外的并发设计决策(请参阅
python-concurrency-performance)。
快速参考
- 测试可观察行为,而不是内部实现。
- 保持单元测试快速且确定。
- 在被测单元使用的模块边界和导入位置处打补丁。
- 修复 bug 时添加回归测试——先编写会失败的测试,再修复。
- 在适用时覆盖超时、重试、取消和清理。
- 针对多个 Python 版本:使用带有 uv 后端的 nox;针对依赖矩阵进行参数化。
- 针对 free-threaded Python:使用
pytest-run-parallel,设置PYTHON_GIL=0,并始终设置 CI 超时。
测试替身
有关完整的 Meszaros 分类(Dummy、Fake、Stub、Spy、Mock)、经典派与 Mock 派的区别,以及一般性指导,请参阅 writing-tests → references/test-doubles.md。
Python 专属说明:
unittest.mock 的命名容易造成误解。
Mock 和 MagicMock 名称中虽有“Mock”,但默认表现为 Stub——通过 return_value 或 side_effect 配置后,它们会返回预设值,但不会自行验证调用。
只有调用 .assert_called_with() / .assert_called_once_with() 时,它们才会成为 Mock(行为验证)。
优先断言可观察输出。
使用 create_autospec() 或 autospec=True。
普通的 Mock() 会静默接受任意参数。
create_autospec(SomeClass) 会强制遵循真实方法签名——调用位置不匹配会立即导致测试失败,而不是等到生产环境中才暴露 TypeError。
在被测模块使用的导入位置打补丁,而不是原始定义位置:
# Wrong: patches the definition; callers in other modules are unaffected
patch("requests.post", ...)
# Right: patches where your module imported it
patch("mymodule.client.requests.post", ...)
如果项目已在使用 pytest-mock,可考虑使用其 mocker fixture。
它集成了 pytest 的 fixture 生命周期,会自动处理拆卸,也避免在测试函数上层层叠加装饰器。
如果 pytest-mock 尚非依赖项,unittest.mock.patch 已足够——不值得仅为使用便利而添加依赖。
优先使用 Fake,而不是深层 Mock 链。
相比多层 Mock() 链,更推荐内存实现(如 FakeRepo、以 dict 为后端的存储)。
模拟生产环境调用图的链条很脆弱,也表明 I/O 尚未与逻辑解耦。
针对变更的诊断
- 依赖项更新:运行
uv run pytest scripts/test_uv_security_audit.py scripts/test_pypi_security_audit.py -v。test_uv_security_audit.py使用uv audit检查锁文件,优先使用;test_pypi_security_audit.py是 pip-audit 的备用方案,可在uv audit可运行时自动跳过。 两者发现问题时都会发出警告(而非失败),因此请检查警告。 - 涉及大量异步操作的生命周期变更:运行
pyleak诊断。 - 多 Python 版本支持变更:通过
nox运行完整矩阵。 - 无 GIL 兼容性:在无 GIL 构建(3.13t+)上运行
PYTHON_GIL=0 uv run --python 3.Xt pytest --parallel-threads=auto --timeout=300。
常见错误
- 只覆盖单个写入点 — 测试了规范代码路径,却没有测试会写入相同契约断言值的其他路径(去重快捷路径、缓存快速路径、重试分支)。
每个写入点都需要单独测试。
参见
references/testing-strategy.md§ 多路径和派生字段模式。 - 缺少组合后的不变量检查 — 当
field_a == f(field_b)是必须满足的关系,且每个字段由不同的生产者(解析器、获取器、合并步骤)写入时,阶段级测试无法证明组合后的结果仍满足该不变量。 编写测试,断言组装后输出中的这一关系。 参见references/testing-strategy.md§ 多路径和派生字段模式。 - 模拟得过于深入 — 修补内部实现,而不是模块边界的接缝,会导致测试脆弱且与实现耦合。
- 测试模拟本身 — 只检查模拟调用次数,却不对可观察的输出作断言,无法证明任何行为。
- 缺少回归测试 — 修复 bug 前没有编写能复现问题的测试;bug 还会再次出现。
- 时间/顺序不确定 — 依赖挂钟时间或 dict/set 的顺序,而不是注入时钟并显式排序。
- 跳过清理断言 — 验证了正常路径,却从未断言失败或取消时资源已释放。
- 缺少无 GIL CI 矩阵项 — 发布多线程代码,却没有无 GIL(带
t后缀)的矩阵项;GIL 会掩盖竞态条件,而当无 GIL Python 成为默认版本时,这些问题就会显现。 - 忽略 GIL 重新启用 — 导入没有
Py_mod_gil的 C 扩展会静默地重新启用 GIL;请在导入后检查sys._is_gil_enabled()。 - YAML 版本浮点数 — 在 CI 矩阵 YAML 中不加引号地写入
3.10;它会被解析为3.1,并安装错误的 Python 版本。
参考资料
references/testing-strategy.mdreferences/pytest-practices.mdreferences/async-and-concurrency-testing.mdreferences/reliability-lifecycle-testing.mdreferences/multi-python-testing.mdreferences/free-threaded-testing.mdwriting-tests技能 — 与语言无关的基础知识(AAA、命名、测试替身、测试组合策略)