关于 maintainable-tests
maintainable-tests 面向编写、重构和审查自动化测试的开发者,强调以行为为核心的测试意图、清晰示例、确定性执行及可维护的测试替身。
主要功能
- 01按行为命名测试
- 02编写清晰具体的示例
- 03指导夹具与测试替身选择
- 04确保确定性并隔离副作用
- 05提供审查与验证清单
适用场景
- 01为新行为添加测试
- 02编写回归与边界覆盖
- 03审查测试改动中的脆弱耦合
- 04测试涉及外部副作用或全局状态的代码
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jpcaparas/ skills --skill maintainable-tests安装后在 Claude Code 中输入/maintainable-tests确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdmetadata.jsonREADME.mdskill-card.prompt.mdagents/openai.yamlassets/.gitkeepevals/evals.jsonevals/files/publish-config.spec.tsreferences/doubles-and-boundaries.mdgotchas.mdlegacy-and-characterization.mdnaming-and-intent.mdprinciples.mdreview-rubric.mdside-effects-and-compatibility.mdsource-notes.mdstructure-and-fixtures.mdscripts/analyze_maintainable_tests.pytest_skill.pyvalidate.pytemplates/test-review.md详细介绍
可维护的测试
编写、编辑、重构和评审测试,让几个月后回来的开发者也能理解行为、边界情况,以及每个重要测试存在的原因。
被动触发
只要任务涉及自动化测试、用作测试的示例、fixture、mock、stub、fake、测试数据构建器、回归覆盖、特征测试、不稳定测试,或为提升可测试性而进行的生产代码更改,就在后台加载此技能。对于小型编辑,保持轻量:默默应用核心规则,然后只提及会影响最终实现的测试设计决策。
决策树
只有当某项不明显的测试设计决策需要比这些规则或本地示例更多的细节时,才查阅相关参考资料。主题匹配并不意味着必须查阅参考资料。
-
为新行为添加测试: 根据用户可见规则或领域不变量为每个测试命名,然后使用能说明该行为的具体示例。如果测试意图不明确,请查阅
references/principles.md或references/naming-and-intent.md。 -
修复 bug 或添加回归覆盖: 验证有问题的场景和预期行为,并在已有覆盖充分时加以复用。如果需要保留旧行为或谨慎说明不确定性,请查阅
references/legacy-and-characterization.md。 -
覆盖边界情况: 让正常路径清晰可见,然后覆盖有意义的边界,不要重复已有的充分测试。如果 fixture 或用例组织较为复杂,请查阅
references/structure-and-fixtures.md。 -
测试难以编写,因为生产代码错综复杂或无法 mock: 优先使用现有的安全接缝,包括框架 override 或作用域明确、可可靠恢复的旧代码 patch。只有在必要、适度且已获授权时才重构生产代码。在仅修改测试的工作中,应提出所需的生产代码更改,而不是直接实施。如果边界决策仍未解决,请查阅
references/doubles-and-boundaries.md,或在可用时使用 {{ skill:maintainable-code }}。 -
更改行为前重构旧代码: 复用已有的充分行为覆盖;仅针对重要缺口添加特征测试。标明有意保留的旧行为,并通过小步且经过验证的方式更改生产代码。如果保留约定不明确,请查阅
references/legacy-and-characterization.md。 -
评审测试差异: 优先指出行为出错时仍能通过的测试、脆弱的实现耦合,以及遗漏的边界覆盖,然后再谈风格。对于较难判断的评审问题,请查阅
references/review-rubric.md;其格式不是必须遵循的。 -
选择 mock、stub、fake、fixture 或集成测试: 优先选择能够证明行为的最简单测试替身,并在适配器可能产生偏差时确保有充分的契约或集成覆盖。若选择并不明显,请查阅
references/doubles-and-boundaries.md;已有覆盖也应纳入考虑。 -
测试涉及网络、等待、全局框架状态、环境门控、文件系统/命令行副作用,或多个受支持的依赖版本: 阻止非预期副作用、重置全局状态、测试相关配置维度,并断言确切的产物或能力分支。若隔离或兼容性风险仍未解决,请查阅
references/side-effects-and-compatibility.md。
快速参考
| 情况 | 默认做法 |
|---|---|
| 新领域规则 | 为正常情况编写一个易读的示例,并为有意义的边界编写有针对性的示例 |
| 测试名称 | 说明行为和结果:rejects withdrawals that exceed the current balance |
| 测试主体 | 优先采用 Arrange / Act / Assert 结构,并让设置部分足够靠近,读起来像一个故事 |
| 重复设置 | 仅在辅助函数的名称能保留领域含义时才提取辅助函数 |
| 参数化测试 | 使用具名用例,说明每一行存在的原因 |
| 遗留行为 | 简要说明理由:兼容性、数据迁移、客户约定、缺陷引用,或明确标注原因未知 |
| 难以测试的生产代码 | 复用安全的接缝;仅在必要、适度且处于授权范围内时重构 |
| Mock 过多的测试 | 将附带的交互断言替换为行为断言、伪实现或适配器契约测试 |
| 边界情况 | 指明具体边界,而不只是说“处理无效输入” |
| 测试中的网络或子进程 | 对未计划的调用直接失败;仅针对被测契约明确使用伪造、桩或集成方式 |
| 睡眠或重试延迟 | 用伪调度器或时钟替代真实等待,并在延迟属于行为时断言所请求的延迟 |
| 全局框架状态 | 在每个测试前恢复到已知基线,并在之后清理,尤其是在框架无法隔离状态时 |
| 可配置行为 | 覆盖契约中的各个维度:通常包括默认值、覆盖值和效果;仅在相关时才覆盖环境或能力分支 |
| 文件或 CLI 效果 | 断言退出状态或结果,以及重要的确切路径、类型、内容、备份和取消时保持不变的行为 |
| 兼容性承诺 | 测试最低受支持组合和当前版本;当生产代码支持可选能力时,添加能力存在与否的测试路径 |
| 评审 | 思考这个测试是否能让新维护者了解哪些行为至关重要 |
核心规则
- 将测试视为持续更新的文档。读者应能通过测试名称和示例了解功能、相关术语及其重要边界。
- 优先使用行为名称,而不是实现名称。测试应命名为
rejects expired invitations,而不是returns false from validateInvite。 - 示例应具体且贴合领域。在有助于阐明规则时,使用真实的值、货币、角色、状态、日期和 ID。
- 让意图一目了然。理解行为时,不应要求读者先去查看远处的 fixture 工厂、隐藏的全局设置或框架魔法。
- 当可读性与 DRY 原则冲突时,优先使用 DAMP 测试。只要能让场景更清晰,重复代码通常比巧妙的共享设置更好。
- 每个测试只聚焦一种行为,但要断言证明该行为所需的所有结果。如果状态变化、返回结果和发出的事件共同构成一条可观察规则,就可以放在一起断言。
- 根据契约选择测试替身:存根回答查询,伪造对象模拟简单状态,间谍观察重要效果,模拟对象只用于强制验证必要的交互。
- 不要暴露私有内部细节、固化糟糕的抽象,也不要仅为通过测试而添加宽泛的接口。优先使用现有的安全接缝;只有在必要且适度时才调整生产边界,并且绝不超出仅修改测试的范围。
- 如果仅凭名称无法说明原因,就记录边界情况和遗留行为。未来的读者需要知道该行为是有原则的、历史遗留的、契约要求的,还是临时的。
- 保持测试的确定性。在明确的边界上控制时间、随机性、外部服务、区域设置、时区、并发和持久化。
- 验证失败方式,而不只是成功路径。回归测试应能因其所防范的缺陷而失败。
- 在引入新模式前,先遵循本地测试框架和风格。
- 让意外效果在测试框架中快速暴露。未设存根的网络调用、实际休眠、有破坏性的命令以及共享进程状态都不应悄然通过。
- 使用正确的主体和类型断言真实可观察结果。用布尔存在性检查来比较文件内容,或针对错误路径进行正确断言,都会造成假阳性。
- 对可配置行为,应分别测试契约的决策维度和效果。如果生产环境支持可选能力,就要覆盖具备和不具备该能力的路径。
可维护性测试关卡
完成测试修改前,请先在心中检查以下关卡;条件允许时,也要使用本地工具进行检查:
| 关卡 | 通过条件 |
|---|---|
| 意图 | 测试名称用领域语言说明行为和预期结果 |
| 场景 | Arrange / Act / Assert 或等效阶段清晰易辨 |
| 证据 | 断言证明的是可观察行为,而非偶然的实现细节 |
| 测试夹具 | 设置简洁、有名称,且足够接近,便于理解而无需追根溯源 |
| 边界情况 | 明确写出边界场景,并说明该边界为何重要 |
| 遗留背景 | 历史或兼容性行为附有理由或工单引用 |
| 确定性 | 时间、随机性、I/O、网络、数据库和进程状态均受控 |
| 隔离性 | 未计划的网络调用、等待、有破坏性的命令以及泄漏的全局框架状态会导致失败,或得到明确重置 |
| 兼容性 | 覆盖支持范围内最低和当前的组合;当可选能力属于承诺的一部分时,也要覆盖具备和不具备该能力的分支 |
| 产物证据 | 断言检查准确的路径、值类型、内容和未改变的状态,以证明效果 |
| 测试替身 | 使用能够证明规则的最弱模拟对象、存根、伪造对象或间谍 |
| 生产设计 | 使用现有的安全接缝;任何生产代码重构都必须是必要、适度且获准的 |
| 交接 | 新维护者可以将测试作为了解该行为的入门指南 |
操作工作流
小改动保持精简:使用聚焦的上下文和足够的现有覆盖,无需写成故事或套用固定报告模板。
-
先侦察。 在引入新模式前,先阅读附近的测试、测试夹具、工厂、辅助工具和生产代码路径。
-
明确行为说明。 找出规则,并将无关问题分开处理。只有在有助于消除歧义时,才把它写下来。
-
选择示例。 检查现有覆盖,然后选择能说明实际产品风险的缺失常规、边界或失败场景。
-
让代码便于观察。 使用现有的安全接缝,包括有可靠恢复和隔离机制的作用域内旧代码补丁。如果没有安全验证行为的方法,就提出最小的生产代码重构,并且只在授权范围内执行。不要用真实副作用或泄漏的全局状态来规避这一限制。
-
将测试写成文档。 让准备过程有意义、操作一目了然、断言具体明确。仅在涉及历史背景、不变量或不明显的领域权衡时添加简短注释。
-
验证失败情况和可读性。 运行聚焦测试。在可行时,确保修复前测试会失败,或测试能捕获所防范的回归。以新成员入门资料的标准重新阅读测试。
-
如实报告。 报告覆盖的行为、验证情况和剩余风险;只有在边界决策影响重大时才提及。省略空白章节和常规流程叙述。
可选辅助工具
将辅助工具用作快速审查提示扫描器,而非判定依据:
python3 scripts/analyze_maintainable_tests.py /path/to/project
python3 scripts/analyze_maintainable_tests.py /path/to/project --json
辅助工具会将目录扫描限制在常见测试路径和带注解的 Rust 源文件;明确提供受支持的源文件时,则会直接检查该文件。它会检查已识别的声明中是否存在一组有限的信号,包括名称、断言、测试夹具、替身、耦合、墙上时钟、真实休眠、随机性和旧代码理由;它不会检测网络访问。扫描结果为空并不能证明测试套件质量良好,扫描结果嘈杂也不能证明测试有问题。
指引不再适用时
如果内置测试模式不起作用,或与仓库的运行器冲突,请检查其已安装版本和框架官方文档。优先采用惯用方式验证行为,同时保持副作用隔离;如有检查未执行,请如实标明。提出对规范技能的修正或删除建议,并说明来源/版本及能区分该问题的回归场景。不要悄悄修改已安装的副本,也不要仅为满足此技能示例而添加脆弱测试。
阅读指南
| 需求 | 阅读内容 |
|---|---|
| 默认做法背后的原则 | references/principles.md |
| 将测试命名为行为文档 | references/naming-and-intent.md |
| 准备 / 执行 / 断言、测试夹具、数据集和辅助工具 | references/structure-and-fixtures.md |
| 模拟对象、桩、伪造对象、边界和生产代码拆分 | references/doubles-and-boundaries.md |
| 旧系统、特征化测试和回归理由 | references/legacy-and-characterization.md |
| 副作用隔离、配置矩阵、精确制品断言和兼容性测试 | references/side-effects-and-compatibility.md |
| 按严重程度优先审查测试差异 | references/review-rubric.md |
| 常见陷阱和反模式 | references/gotchas.md |
| 来源影响和改编说明 | references/source-notes.md |
常见问题
- 即使测试很短,如果重要逻辑隐藏在工厂函数或通用辅助函数中,仍然会难以理解。
- 即使测试存在重复,只要重复内容能让独立行为一目了然,仍然易于维护。
- 充斥模拟对象的测试往往记录的是实现决策,而非产品行为。只有当交互本身就是行为时,才使用模拟对象。
- 如果参数化测试中的每一行都只是数据,测试就会变得晦涩。为用例命名,并让预期结果清晰可见。
- 没有说明理由的遗留测试会让困惑长期存在。说明相关行为是有意为之、历史遗留、暂时如此,还是未知。
- 不要把令人头疼的测试视为不可避免。测试带来的痛苦可能是在反馈生产代码需要更清晰的边界。
- 即使匹配器看起来合理,如果其对象类型不正确或指向了错误的产物,也可能什么都证明不了。追溯每条断言,确认它对应可观察到的契约。
- 只在最新依赖版本上通过,并不能证明文档所述的兼容范围。测试最旧的受支持组合,以及可选能力不可用时的回退行为。