测试驱动开发(TDD)
概述
在编写生产代码之前先写测试,确认测试正确失败,再写最少的代码让它通过。
核心原则: 如果你没有看到测试失败,你就不知道它是否测试了正确的东西。
违反规则的字面意思就是违反规则的精神。
何时使用
新功能、Bug 修复、重构、行为变更——始终使用,并从编写生产代码之前启动。
例外(需询问你的人类伙伴): 一次性原型、生成的代码、配置文件。
铁律
没有失败的测试,就不写生产代码
如果已经先写了生产代码,不得把它作为 TDD 证据;先补充能复现目标行为的失败测试,再重新验证实现。
红-绿-重构
红灯 - 编写失败的测试
写一个最小测试展示一个有业务价值的期望行为,名称清晰,优先调用真实代码;只有无法避免时才使用 mock。
验证红灯 - 看它失败
必须执行。绝不跳过。
运行对应的单测命令。
确认:测试失败(不是报错)、失败信息符合预期、失败原因是功能缺失(不是拼写错误)。
测试在实现前通过了? 检查它是否真正覆盖目标行为;若未覆盖,补充或调整测试,不能把这次通过当作红灯证据。
测试报错了? 修复错误,重新运行直到它正确地失败。
绿灯 - 最少代码
写刚好使测试通过的最少代码,不添加测试未要求的功能或无关重构。
验证绿灯 - 看它通过
必须执行。 确认:测试通过、其他受影响测试仍然通过,相关输出无错误;无关的既有警告需识别并记录。
测试失败了? 修改代码,不是测试。其他测试失败了? 立即修复。
重构 - 清理代码
只有在绿灯之后才重构:消除重复、改善命名、提取辅助函数。保持测试绿灯。不要添加行为。
重复
为下一个功能写下一个失败的测试。
TDD 循环清单
在结束 TDD 循环之前:
- 每个新增或改变的可测试行为都有覆盖
- 在实现之前看到每个测试失败
- 每个测试因预期原因失败(功能缺失,不是拼写错误)
- 测试覆盖目标行为,而不是只验证实现细节
- 为每个测试编写了最少代码使其通过
- 当前行为相关测试及受影响测试通过
- 相关测试输出无错误;无关的既有警告已识别并记录
- 测试使用真实代码(只在不可避免时用 mock)
- 覆盖了边界情况和错误场景
不能全部勾选?你跳过了 TDD,需要回到缺失的红灯、绿灯或重构步骤。全量验证、需求逐项核对、代理 diff 验证和最终完成声明由 verification-before-completion 负责。
TDD 完成后的衔接
TDD 在编写生产代码之前启动,并持续到红灯、绿灯和重构循环完成;它验证的是每个行为的正确性(测试先失败后通过)。
这不等同于任务完成。TDD 循环完成、代码及相关测试就绪后,必须经过 verification-before-completion 做最终完成声明验证:
- TDD 验证:每个测试红-绿循环已走完 → 证明代码做了正确的事
- verification-before-completion 验证:完成声明证据、需求逐项核对、代理 diff 验证和全量验证结果新鲜且完整 → 证明你有权说"完成了"
跳过 verification-before-completion 直接宣称完成,等于用技术正确性替代了需求满足度——测试全过不代表需求全实现。
调试集成
发现 bug?写一个重现 bug 的失败测试。按 TDD 循环走。测试既证明了修复有效,又防止了回归。
绝不在没有测试的情况下修复 bug。
References 读取条件
| 文件 | 什么时候读 |
|---|---|
references/anti-rationalization.md |
想着"就这一次跳过 TDD"或"这次情况不同"时 |
references/testing-anti-patterns.md |
添加 mock 或测试工具时 |