测试驱动开发
先编写失败的测试。看到它失败。编写最少的代码使其通过。重构。 一个立即通过的测试证明了什么——你从未见过它捕获一个错误。
铁律: 没有生产代码能在没有失败测试的前提下编写。如果你在测试之前写了代码,删除它并重新开始。“保留作为参考”是伪装的测试后补——删除意味着彻底清除。
何时使用
- 实现任何新功能或行为
- 修复一个 Bug(先用测试复现它——即“证明模式”)
- 重构或更改现有行为
- 触发词:“tdd”、“test-driven”、“red green refactor”、“测试驱动开发”、“红绿重构”、“测试驱动”
不适用于: 为已编写的代码生成测试(应使用 test-generation);一次性原型或纯配置(直接实现即可)。
例外情况(请与用户确认): 一次性原型、生成代码、纯配置更改且没有行为影响。
步骤
0. 首先发现代码栈
循环是通用的,但命令不是。在第一个测试之前,找出这个仓库如何测试:读取 package.json / pyproject.toml / Cargo.toml / pom.xml / go.mod,检查是否有 ./gradlew、Makefile、CI 工作流以及现有测试文件的命名方式。永远不要假设 npm test——使用仓库自身的命令执行每一步 RED、GREEN 以及最终验证。
1. RED——编写一个失败的测试
一个行为,一个测试,一个垂直切片(追踪子弹,响应上一个循环教给你的内容)。编写你希望存在的测试——它描述的是期望的 API,而非实现细节。将其命名为一个规范(例如“拒绝空邮件地址”,而不是“test1”)。
在编写测试之前先达成接缝(公共接口下的测试点)的共识。没有经过确认接缝的测试——提前达成接缝共识可以把测试精力集中在关键路径上,而不是每个边界情况。优先使用真实代码而非模拟;如果必须对依赖进行替身,使用最简单有效的替身(真实 > 假对象 > 存根 > 模拟——参见 references/test-strategy.md)。模拟隔离的是边界,而非被测试对象本身。
2. 验证 RED——看它失败
运行聚焦的测试命令。确认:
- 它失败了(而非报错——报错意味着需要修复设置并重新运行)
- 它以正确的理由失败(功能缺失,而非拼写错误)
- 一个立即通过的测试意味着你在测试现有行为——修复测试
3. GREEN——编写最少的代码
能让测试通过的最简代码。没有推测性的功能、没有“灵活性”、没有重构相邻代码。YAGNI。不要向生产类中添加仅用于测试的方法(如 destroy()、reset())——将清理代码放入测试工具中。
4. 验证 GREEN——看到它通过
运行聚焦的测试命令,然后运行整个测试套件。确认:
- 新测试通过
- 没有其他测试被破坏(如果破坏了,立即修复——不要跳过)
- 输出干净(没有警告,没有跳过测试)
5. REFACTOR——清理代码
仅在测试通过的情况下:提取辅助函数、改进命名、消除重复。每次重构步骤后都运行测试。不要增加行为。重构属于审查阶段,而非红→绿实现周期。
6. 提交
将测试和实现作为一个垂直切片一起提交。然后开始下一个失败的测试。
错误修复——证明模式
不要从修复开始。编写一个复现该错误的测试(RED 确认错误存在),然后修复它(GREEN 证明修复有效)。这个测试将保留作为回归防护。永远不要在没有复现测试的情况下修复错误。
验证
- 每个新行为都有一个在实现之前失败的测试
- 使用仓库自身的测试命令运行完整套件,全部通过
- 错误修复包含一个在修复之前失败的复现测试
- 测试名称描述的是行为,而非实现
- 没有测试被跳过或禁用;生产代码中没有仅用于测试的方法
参考资料
- ${CLAUDE_PLUGIN_ROOT}/references/engineering-principles.md — 每项技能共享的原则
- references/test-strategy.md — 测试金字塔、测试替身层级(真实 > 假对象 > 存根 > 模拟)、DAMP 优于 DRY
- references/testing-anti-patterns.md — 模拟滥用、仅测试方法、部分模拟、门控函数
- references/mocking.md — 何时模拟、设计可模拟性
- references/good-tests.md — 好测试与坏测试示例、循环论证测试