测试驱动开发(Matt Pocock)
测试驱动开发。当用户希望以测试优先的方式构建功能或修复 Bug、提到 'red-green-refactor',或需要集成测试时使用。
测试驱动开发
理念
核心原则:测试应通过公共接口验证行为,而非验证实现细节。代码可以彻底改变,但测试不应随之改变。
好的测试 是集成风格的:它们通过公共 API 走完真实的代码路径。它们描述系统 做了什么,而非系统 如何做。一个好的测试读起来就像一份规格说明——"用户可以用有效的购物车结账"准确地告诉你存在什么能力。这些测试能够在重构中存活,因为它们不关心内部结构。
坏的测试 与实现紧密耦合。它们 Mock 内部协作者、测试私有方法,或通过外部手段进行验证(比如直接查询数据库,而不是使用接口)。警示信号是:当你重构时测试失败了,但行为并没有改变。如果你重命名了一个内部函数而测试失败,那么这些测试测的是实现,而非行为。
反模式:水平切片
不要先编写所有测试,然后再编写所有实现。 这就是"水平切片"——把 RED 理解为"编写所有测试",把 GREEN 理解为"编写所有代码"。
这会产生 糟糕的测试:
- 成批编写的测试测的是 想象出来的 行为,而非 实际的 行为
- 你最终测的是事物的 形状(数据结构、函数签名),而非面向用户的行为
- 测试对真实的变更变得不敏感——行为出错时它们通过,行为正常时它们却失败
- 你超出了自己的视野范围,在理解实现之前就锁定了测试结构
正确的做法:通过曳光弹(tracer bullets)进行垂直切片。一个测试 → 一个实现 → 重复。每个测试都回应你从上一个循环中学到的东西。因为你刚刚写完代码,所以你确切知道哪些行为重要以及如何验证它。
WRONG (horizontal):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT (vertical):
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
...
工作流
1. 规划
在探索代码库时,请阅读 CONTEXT.md(如果存在),这样测试名称和接口词汇就能与项目的领域语言相匹配,并尊重你所触及区域中的 ADR(架构决策记录)。
在编写任何代码之前:
- 与用户确认需要哪些接口变更
- 与用户确认要测试哪些行为(确定优先级)
- 识别深模块的机会(小接口,深实现)
- 列出要测试的行为(而非实现步骤)
- 获得用户对计划的批准
询问:"公共接口应该是什么样子?哪些行为最需要测试?"
你无法测试所有东西。 与用户确认到底哪些行为最重要。把测试精力集中在关键路径和复杂逻辑上,而不是每一种可能的边界情况。
2. 曳光弹
编写一个测试来确认系统的一件事:
RED: Write test for first behavior → test fails
GREEN: Write minimal code to pass → test passes
这就是你的曳光弹——证明这条路径端到端是通的。
3. 增量循环
对于每个剩余的行为:
RED: Write next test → fails
GREEN: Minimal code to pass → passes
规则:
- 一次一个测试
- 只编写刚好能让当前测试通过的代码
- 不要预判未来的测试
- 让测试聚焦于可观察的行为
4. 重构
在所有测试通过后,寻找重构候选项:
- 抽取重复
- 加深模块(把复杂性藏到简单接口之后)
- 在自然的地方应用 SOLID 原则
- 思考新代码揭示了关于现有代码的哪些信息
- 在每个重构步骤之后运行测试
绝不在 RED 状态下重构。 先到达 GREEN。
每个循环的检查清单
[ ] Test describes behavior, not implementation
[ ] Test uses public interface only
[ ] Test would survive internal refactor
[ ] Code is minimal for this test
[ ] No speculative features added