测试驱动开发(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
...
在探索代码库时,请阅读 CONTEXT.md(如果存在),这样测试名称和接口词汇就能与项目的领域语言相匹配,并尊重你所触及区域中的 ADR(架构决策记录)。
在编写任何代码之前:
询问:"公共接口应该是什么样子?哪些行为最需要测试?"
你无法测试所有东西。 与用户确认到底哪些行为最重要。把测试精力集中在关键路径和复杂逻辑上,而不是每一种可能的边界情况。
编写一个测试来确认系统的一件事:
RED: Write test for first behavior → test fails
GREEN: Write minimal code to pass → test passes
这就是你的曳光弹——证明这条路径端到端是通的。
对于每个剩余的行为:
RED: Write next test → fails
GREEN: Minimal code to pass → passes
规则:
在所有测试通过后,寻找重构候选项:
绝不在 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