测试驱动开发
TDD 就是红 → 绿循环。这个 skill 是让那个循环产出值得保留的测试的参照:什么是好测试、测试该落在哪、反模式、循环的规矩。每节在每个循环都适用——循环之前和之中查阅,不是之后。
探索代码库时,读 CONTEXT.md(如果存在),让测试名和接口术语匹配项目的领域语言;尊重你要触碰区域的 ADR。
什么是好测试
测试通过公开接口验证行为,不验证实现细节。代码可以彻底重写;测试不应该变。好测试读起来像规约——"user can checkout with valid cart"准确告诉你存在什么能力——并在重构后存活,因为它不关心内部结构。
示例见 tests.md,mock 指引见 mocking.md。
接口——测试落在哪
接口(seam)是你测试的公开边界:你能观察行为、又不把手伸进内部的那一面。测试落在接口上,永远不直接对内部下手。
只在预先商定的接口上测试。 写任何测试之前,把要测的接口列出来、跟用户确认。没确认过的接口不写测试。你没法什么都测——预先约定接口,才能让测试精力落在关键路径和复杂逻辑上,而不是散到每个边界情况。
问:"公开接口是什么?该测哪些接口?"
当那个接口的形状本身存疑时——模块有多深、接口放在哪、接口该暴露什么——用 /codebase-design skill 获取术语。它是 模块、接口、深度、适配器、杠杆、局部性 这些术语的共享来源,是一份供查阅的参照,不是一次要跑的会话。
反模式
- 耦合实现 ——mock 内部协作者、测私有方法,或通过旁路验证(查数据库而不是走接口)。特征:行为没变,一重构测试就挂。
- 同义反复 ——断言的期望值用代码自身的方式重新算了一遍(
expect(add(a, b)).toBe(a + b)、用同样方式手工算出再贴上的快照、常量跟自己相等),所以它构造上就过,永远没法跟代码不一致。期望值必须来自独立的真值源——已知正确的字面量、手算的例子、规约。 - 水平切片 ——先把所有测试写完,再写全部实现。批量测试验证的是_想象出来的_行为:你测的是东西的_形状_,而不是用户面对的行为;测试对真实改动变得不敏感;你在还没理解实现之前就先把测试结构定死了。改成垂直切片 ——一个测试 → 一段实现 → 重复,每个测试是一条 tracer bullet,回应上一个循环教给你的东西。
循环的规矩
- 先红再绿。 先写挂掉的测试,再写刚好够它过的代码。不要预判未来的测试,不要加投机性的功能。
- 一次一片。 每个循环一个接口、一个测试、一段最小实现。
- 重构不在循环内。 它属于 review 阶段(见
code-reviewskill),不属于红 → 绿的实现循环。