技能手册
27 / 27 AI 工程技能 · 7 分钟读完 · 更新于 2026 年 8 月 24 日

/tdd 技能

红—绿—重构循环的规则。

安装此技能

npx skills@latest add mattpocock/skills --skill=tdd

然后在你的编码 agent 里输入 /tdd 。

本页目录

它做什么

tdd 测试先行地构建一个功能或修复一个 bug:一个失败测试,然后刚好足够让它通过的代码,然后下一个行为。它承载着让那个循环产出值得保留的测试的标准:什么是一个好测试,测试放在哪里,模拟(mock)是干什么用的,以及那三种让整套测试变得一文不值的反模式(anti-pattern)。

它不会在你事先没有商定的接缝(seam)上写任何测试。在任何测试存在之前,它先点名它打算测试的那些公共边界,并停下来等你确认。测试的精力是有限的,而这一步把它花在关键路径上,而不是每一个边缘情况上。tdd 也是一份参考(reference),不是驱动者(driver)。它包含这个循环的规则,而别的东西(你,或者 implement)运行应用这些规则的会话(session)。

什么时候用它

输入 /tdd,或者当一个任务匹配时由 agent自动去拿它:测试先行地构建一个功能或修复一个 bug,或者当你说「red-green-refactor」时。

当有一个具体行为要构建、有输入也有可观察的输出,而且你想要能扛过重构的测试时,就用它。

你的情况该去哪
一个输入输出都已定义的行为(业务逻辑、请求/响应契约(contract)、一次变换、校验)tdd
行为还没有被钉死to-spec,它也会在任何代码写下之前商定测试接缝
问题实际上是接口的形状,而不是测试codebase-design
你有一份规范文档(spec)或工单(tickets),想让人替你跑完整个构建implement,它按工单驱动 tdd
配置、接线、胶水、类型标注、直白的 CRUD 委托这里没有合适的;见下面那个开放缺口

最后一行是一个真实的缺口。技能决定接缝放在哪里,但其中没有任何东西决定一项改动是否值得这个循环。如果你把它跑在一项没有独立事实来源可供断言的改动上,你得到的测试只是在复述实现。那就是技能所警告的同义反复(tautological)反模式,只是从另一个方向到达。它是 issue #746,仍然打开。在它关闭之前,这个判断你自己做,或者把规则写进你的 CLAUDE.md。

前提条件

需要安装 codebase-design。tdd 过去自带深度模块(deep module)和接口设计的笔记;v1.0 把它们删掉,改用共享技能,而 tdd 现在使用它的接口设计词汇。没有别的了;这个技能是无状态(stateless)的,不写自己的文件。

循环,以及它运行的接缝

这个技能建立在三个术语上。

红—绿(red-green)。 写失败测试,然后只写刚好够让它通过的代码。不要为下下个测试写代码。没有重构阶段。技能在 2026 年 6 月去掉了它,因为 agent 几乎从不执行它,而且审查与实现作为分开的会话效果更好。重构属于 code-review。

垂直切片(vertical slice)。 在一道接缝上写一个测试,然后写最小的实现,然后重复。第一个循环是一颗曳光弹(tracer bullet),端到端地证明一条路径。相反的做法是水平切片(horizontal slicing):先写所有测试,再写所有代码。批量写出的测试验证的是想象出来的行为。它们检查事物的形状,而不是用户做什么,并且在你理解实现之前就把你绑定到一种测试结构上。

预先商定的接缝。 接缝是你在不伸手进去的前提下观察行为的公共边界。这条规则没有例外。测试绝不放在未经确认的接缝上。在完整链条里,接缝是更早的时候在 to-spec 里商定的:「/tdd 被要求在预先商定的测试接缝上工作,/code-review 检查只使用了商定的测试接缝。」单独调用时,tdd 直接问你。

它被写来防止的三种反模式:

反模式识别标志
与实现耦合(implementation-coupled)你重命名一个内部函数时测试就坏了,尽管行为没有改变。模拟内部协作者、断言调用次数、用数据库查询而不是接口来验证。
同义反复期望值按代码计算它的方式算出来,所以测试在构造上就会通过。期望值必须来自别处:一个已知正确的字面量、一个算好的例子、规范文档。
水平切片在任何实现之前就落下一批测试。

模拟只用于系统边界:外部 API、时间、随机性,有时是文件系统或数据库。绝不模拟你自己的模块。

常见问题

为什么它不重构?描述里说的是「red-green-refactor」。

因为重构阶段被移除了,而描述没有。移除是刻意的。agent 几乎从不做那一步,而把实现和审查保留在分开的会话里效果更好。结果是否还算按书本上的 TDD,不如这个循环是否产出更好的代码重要。触发短语和正文之间的不一致记录为 issue #589,仍然打开,所以「red-green-refactor」继续作为一句能触发这个技能的话起作用。你得到的是红—绿,重构在 code-review 里。

它让我选一道测试接缝,而我完全不知道该选哪个。

这是关于这个技能报告得最多的摩擦(issue #607)。提示词只按名字列出候选接缝,完全不说每一道能抓住什么、漏掉什么,所以你是在标签之间做选择。目前还没有修法发布。实际的变通办法是在回答之前先向 agent 要权衡:组件级接缝漏掉了什么而集成接缝能抓住,它又慢了多少。这也是为什么链条在 to-spec 里提前商定接缝,在那里你能看到整个功能,而不是只看一个提示词。

它先写了实现,再写测试,尽管技能说的是先红。

这会发生。一位用户就此逼问过模型(model),得到了一个异常诚实的回答:「我知道技能说了『一次一个测试,看着它因为正确的原因失败』。我读了。我只是默认走到了我平时的习惯。」技能接受这一点。没有任何指令能让 agent 100% 遵守,而更严格的措辞会为了很少的收益限制 agent 的创造力。即使 agent 不严格遵循,这个循环也值得跑,因为结果总体上仍然更好。如果某个特定切片上严格遵守很重要,那就盯着这次运行,而不是信任技能去强制执行。

它应该先写浏览器测试或端到端测试吗?

通常不应该,而技能也不会阻止它。一位用户报告说,agent 先写了一个 Playwright 测试,然后花了一个很长的循环反复重跑它,最后得出结论说测试坏在一个还不存在的功能上。浏览器测试慢到红—绿反馈环不再划算。在你仓库的 CLAUDE.md 里写明浏览器测试在行为能工作之后才写。

/tdd 会取代 /implement,或者课程里的 /do-work 吗?

不会。/tdd 记录方法论;/implement 是一个简单的工作→反馈→提交循环,是 /do-work 的直接替代品。课程里单一的 /do-work 步骤现在拆成了 /implement、/tdd 和 /code-review。如果你在问该拿哪个去跑一张工单,答案几乎总是 /implement。

深度模块和接口设计的指导去哪了?

在 v1.0 里进了 codebase-design,并被泛化,让好几个技能共享一套词汇。refactoring.md 同时也离开了;重构现在是 code-review 的活,那个技能带着 Fowler 的坏味道基线(smell baseline)。

它知道我其他的工单吗?

不知道。跑在一张工单上时,它可能提出属于一张兄弟工单的工作,因为它看不到 issue 图的其余部分(issue #129)。这不是 tdd 的活。把规范文档和工单一起传进去有帮助;一开始就把工单切成合适的大小更有帮助。

它生效的标志

  • 它在任何测试文件存在之前就停下,点名它打算测试的接缝,然后等待。
  • 出现一个测试,变红,得到刚好够它通过的代码,然后才出现下一个测试,而不是一批测试后面跟着一批代码。
  • 测试名读起来像能力(「user can checkout with valid cart」),而不是内部细节(「checkout calls paymentService.process」)。
  • 断言里的期望值是你能在规范文档里追溯到的字面量,而不是按代码计算的方式重新算出来的值。
  • 重命名一个内部函数不会让测试套件里任何东西坏掉。
  • 模拟只出现在外部边界(支付 API、时钟),绝不围绕你自己的模块。

它在体系中的位置

tdd 在主链的构建步骤内部运行;它本身不是一步:

grill-with-docs → to-spec → to-tickets → implement → code-review → retro

to-spec 提前商定测试接缝,implement 按工单驱动 tdd,而 code-review 事后检查只使用了商定的接缝,并接手 tdd 不再做的重构。它的另一个邻居是 codebase-design,那是 tdd 所使用的接缝与深度模块词汇的共享来源。当你有一个具体行为要构建、又没有完整规范文档参与时,你也可以单独用它。当你不确定哪个技能适合你的情况时,ask-matt 会为你路由。


原文:aihero.dev/skills-tdd