SuperAPI AI 工具集

python-testing-conventions

测试与质量

查看项目

更新于 10-16 04:02

技能介绍

用途

本技能面向个人、由智能体维护的 Python 项目,专门负责测试套件的约定。它于 2026-08-31 从 python-conventions 中拆分出来:后者保留设计与风格默认值,本技能掌管测试。这里不包含工具配置,pytest 自身的配置属于仓库的质量工具链,不在本技能范围内。

每条约定都会说明它是在覆盖模型的默认直觉,还是仅仅确认已有直觉。有能力的模型已经会主动参数化取值矩阵,也会主动使用 tmp_path。本技能针对的,是模型被放任时会漂移的地方:把同一段 arrange 代码内联进每个测试,而不是提升为 fixture;以及在本可以掌控真实对象的地方伸手去拿 mock。

主要能力:fixture 优先

测试所需的任何准备——临时目录树、伪造的 HOME、被替换的 c.run、构造好的对象、monkeypatch 的环境——都应是 pytest fixture(两个文件都需要时就放到 conftest.py),而不是每个测试体顶部手写的若干行。理由有两个,第二个更重要:一是消除机械重复;二是让套件以三种不同方式做同一件事这件事浮出水面——三个手写的“造一个假仓库”会长期藏在三个测试体里,而三个分别名为 fake_repo、tmp_repo 和 repo_dir 的 fixture 会并排在 conftest.py 里,然后被合并。先考虑内置 fixture(tmp_path、monkeypatch、capsys、caplog),再写会重复实现它们的辅助函数。辅助函数只作为兜底,用于需要按调用传参、fixture 无法接收的准备;即便如此,返回工厂的 fixture(如 make_repo(name))通常也够用。

伪造 HOME 涉及不止一个变量,在 Windows 上首次运行还会多出若干陷阱:expanduser 在 POSIX 上读 HOME、在 Windows 上读 USERPROFILE,只设置前者的 fixture 会在每个测试里写入真实用户配置。写入 TOML 或 JSON 的路径要用 path.as_posix();在 TOML 基本字符串里反斜杠会开启转义,C:\Users 是非法的 \U。所有 read_text/write_text 都应传 encoding="utf-8",测试与代码皆然。伪造运行器的表键和路径列表比较同样要经过 as_posix(),因为 Path("/repo") 会渲染成 \repo。任何平台接缝(被测模块中的 WINDOWS = os.name == "nt")都要钉到 fixture 所针对的那个分支,否则在 Linux 上写的测试会读到真实平台。

fixture 作用域

作用域应取保持正确前提下的最窄范围。针对 python-conventions 中的模块单例模式,在模块或会话作用域构造昂贵对象,但通过函数作用域的 fixture 重置其可变状态。宽作用域 fixture 内的 monkeypatch 会在整个作用域内持续生效,而不只影响一个测试,这是真实且静默的跨测试泄漏来源。

DAMP 与 DRY

这是与 python-conventions 中生产代码 DRY 决策不同的另一个维度,不是对它的重新推导:准备机制(fixture/辅助函数,即“怎么做”)保持 DRY;测试所验证的场景(即“验证什么”)保持显式,并在该测试中自上而下可读。parametrize 是处理真实输入到期望值矩阵的日常手段,比复制 N 份测试体更显式,因为变化的值与固定逻辑被隔离开来;当取值不再自明时,给它们附上 id。界线是:如果新增一个用例意味着新增一个值,就用 parametrize;如果意味着改变测试逻辑(一个分支、不同的准备、不同的断言),就写一个新测试。真正被警告的,是把本质上不同的场景塞进一个带分支的巨型测试,或把场景藏在一个名字没说明它断言什么的辅助函数里。

模型的默认行为整体上是确认,但有一个方向需要覆盖:模型会主动参数化取值矩阵,这没错;但它不会可靠地把准备提升为 fixture,放任不管时会把同样的三行 arrange 内联进它写的每个测试。另一个需要覆盖的窄点:python-conventions 的模块化抽象直觉可能泄漏进来,把逻辑不同的场景折叠成一个带分支的参数化测试,或折进一个拥有断言的 check_* 辅助函数。

不要在测试中运行会改动代码的命令

除非测试真正的主旨就是那个改动,否则不要在测试所执行的行为里运行会改动代码的命令。在断言前运行修复、格式化或自动纠正命令,会静默掩盖只检查版本本可抓到的确切缺陷。优先在测试中使用命令的只检查或试运行形式。模型的默认行为在这里需要覆盖:模型习惯性地使用质量或构建工具的“完整”先修复再检查调用,本条专门在测试中阻止这一直觉,因为它会静默收窄测试能捕获的范围。

不要对套件能真实运行的东西做替身

默认不做 mock、fake 或 stub 的依赖,是套件能掌控其完整生命周期的依赖——进程内,或作为它启动并停止的子进程。SQLite 文件、临时目录、本地队列、自己入口程序的子进程:运行真实对象。第三方 HTTP API 是界线的另一边,为它手写替身是正确的,而非妥协。判断标准是上述生命周期测试,而不是一份技术清单:清单会过时,还会引发关于成员资格的争论,而“这个套件能否启动并停止它”能自行回答新情况。

理由:这正是 db-defaults 已经据以选择的前提。那里的每个默认值都部分基于“无需 docker 或云即可在本地 pytest 测试”,替身化数据库会扔掉当初选择该依赖的理由。真实不等于沙箱,运行真实服务让这一区别更显著:测试若触达 Path.home() 就会写入真实用户目录;被测真实服务与 fake 一样需要自己的临时状态。当框架单例让进程内的安排不诚实时,答案是子进程 fixture,而不是 mock:针对自己的临时状态启动真实入口,复现部署形态,而不是假装耦合不存在。给它一个有界的就绪等待,失败时输出子进程的输出;无界条件在永远无法成真时会挂起而非失败。模型默认行为需要覆盖:一旦测试要打开文件或套接字,模型就会伸手去拿内存 fake,而“测试不应触碰磁盘”读起来像是纪律之举;但在套件本可以自己掌控真实对象的地方,这是错误的直觉。

使用方式

本文件在安装时被复制到 ~/.agents/skills/python-testing-conventions,从不做符号链接,因此编辑已部署副本属于本地漂移,不会到达其他机器。应编辑其安装来源仓库中的源文件,推送后重新运行 skills add <该来源> --global --skill python-testing-conventions,以刷新每个项目的副本。如果是从他人的仓库而非自己的 fork 安装,源属于对方,应到那里提 issue 或 pull request。

其他资料

references/rationale.md 给出完整理由,包括所查阅的来源、DAMP 与 DRY 争论的实际现状,以及上述默认值背后的 fixture 作用域推理。references/snippets/testing.py 是可运行的草稿,展示这里描述的 fixture 与 parametrize 形态。