TDD 是一个测试驱动开发技能,适用于用户希望以测试优先方式构建功能或修复缺陷、提到“red-green-refactor”(红-绿-重构),或需要集成测试的场景。
核心理念
测试应通过公共接口验证行为,而不是实现细节。代码可以完全改变,测试不应改变。
好的测试是集成风格的:它们通过公共 API 走真实的代码路径,描述系统“做什么”而非“怎么做”。好的测试读起来像规格说明,例如“用户可以用有效购物车结账”清楚说明存在什么能力。这类测试能经受重构,因为它们不关心内部结构。
坏的测试与实现耦合:它们模拟内部协作者、测试私有方法,或通过外部手段验证(例如直接查询数据库而不使用接口)。警示信号是:重构时行为没变,测试却失败。如果重命名一个内部函数导致测试失败,那这些测试测的是实现,不是行为。
反模式:水平切片
不要先写完所有测试,再写所有实现。这就是“水平切片”——把 RED 当成“写所有测试”,把 GREEN 当成“写所有代码”。
这会产出糟糕的测试:批量编写的测试测的是想象的行为,而非实际行为;最终测的是事物的形状(数据结构、函数签名),而不是面向用户的行为;测试对真实变化不敏感——行为坏了却通过,行为正常却失败;在理解实现之前就锁定测试结构,等于车灯照不到前方就往前开。
正确做法:通过曳光弹做垂直切片。一个测试 → 一段实现 → 重复。每个测试都回应上一轮学到的东西。因为刚写完代码,你清楚哪些行为重要、如何验证。
错误(水平): RED: test1, test2, test3, test4, test5 GREEN: impl1, impl2, impl3, impl4, impl5
正确(垂直): RED→GREEN: test1→impl1 RED→GREEN: test2→impl2 RED→GREEN: test3→impl3 ...
工作流
- 规划
探索代码库时,阅读 CONTEXT.md(如果存在),使测试名称和接口词汇与项目领域语言一致,并遵守所触及区域的 ADR。
写任何代码之前:
- 与用户确认需要哪些接口变更
- 与用户确认要测试哪些行为(排优先级)
- 识别深模块(小接口、深实现)的机会——运行 /codebase-design 技能获取词汇和可测试性检查
- 列出要测试的行为(不是实现步骤)
- 获得用户对计划的批准
可以问:“公共接口应该是什么样?哪些行为最重要、最需要测试?”
你无法测试一切。与用户确认哪些行为最重要,把测试精力集中在关键路径和复杂逻辑上,而不是每个可能的边界情况。
- 曳光弹
写一个测试,确认系统的一件事:
RED: 为第一个行为写测试 → 测试失败 GREEN: 写最小代码使其通过 → 测试通过
这就是曳光弹——证明路径端到端可用。
- 增量循环
对每个剩余行为:
RED: 写下一个测试 → 失败 GREEN: 最小代码使其通过 → 通过
规则:
- 一次一个测试
- 只写足够通过当前测试的代码
- 不要预判未来的测试
- 让测试聚焦于可观察行为
- 重构
所有测试通过后,寻找重构候选:
- 提取重复
- 加深模块(把复杂性移到简单接口之后)
- 在自然之处应用 SOLID 原则
- 思考新代码揭示了现有代码的什么
- 每步重构后运行测试
绝不要在 RED 状态下重构。先到 GREEN。
每轮检查清单
[ ] 测试描述行为,而非实现 [ ] 测试只使用公共接口 [ ] 测试能经受内部重构 [ ] 代码对当前测试是最小的 [ ] 没有添加投机性功能
限制
- 当工作流指定了上游工具、账号、API 密钥或本地环境时,需要相应准备。
- 未经用户明确批准,不授权执行破坏性、生产环境、付费或对外发消息的操作。
- 在把生成的产物或建议当作最终结果之前,应针对用户的真实来源进行验证。