Tdd
它的作用¶
tdd 以测试优先的方式构建功能或修复缺陷:先写一个失败的测试,然后只写刚好能让它通过的代码,接着处理下一个行为。它承载了让该循环产出值得保留的测试的标准:什么是好测试、测试应放在哪里、mock 的用途,以及让测试套件毫无价值的三种反模式。
它不会在你尚未事先同意的接缝上编写任何测试。在任何测试存在之前,它会列出打算测试的公共边界,并停下来等待你确认。测试精力是有限的,这一步将其花在关键路径上,而不是每个边缘情况上。tdd 也是一个参考,而不是驱动者。它包含循环的规则,而由其他东西(你,或 implement)来执行应用这些规则的 会话。
何时使用它¶
输入 /tdd,或者当任务符合时,代理 会自动使用它:以测试优先方式构建功能或修复缺陷,或者当你说 “red-green-refactor” 时。
当存在一个要构建的具体行为,具有输入和可观察输出,并且你希望测试能在重构后存活时,使用它。
| 你的情况 | 去哪里 |
|---|---|
| 具有明确输入和输出的行为(业务逻辑、请求/响应契约、转换、验证) | tdd |
| 行为尚未确定 | to-spec,它还会在任何代码编写前确定测试接缝 |
| 真正的问题是接口的形状,而不是测试 | codebase-design |
| 你有 规格 或 工单,并希望替你运行整个构建 | implement,它会按工单驱动 tdd |
| 配置、接线、胶水代码、类型注解、直接的 CRUD 委托 | 这里没有合适的;见下方未解决的缺口 |
最后一行是一个真实缺口。该技能决定接缝放在哪里,但其中没有任何内容决定一个变更是否值得走这个循环。如果你在一个没有独立事实来源可供断言的变更上运行它,你会得到一个重述实现的测试。这正是该技能警告的同义反复反模式,只是从另一个方向到达。它是 issue #746,目前仍处于开放状态。在它关闭之前,你需要自己做出判断,或者把这条规则写入你的 CLAUDE.md。
前提条件¶
需要安装 codebase-design。tdd 曾经自带自己的深模块和接口设计说明;v1.0 删除了它们,转而采用共享技能,并且 tdd 现在使用其接口设计词汇。没有其他要求;该技能是 无状态,并且不写入任何自己的文件。
循环及其运行的接缝¶
该技能基于三个术语。
红-绿。 编写失败的测试,然后只写刚好能让它通过的代码。不要为下一个测试编写代码。没有重构阶段。该技能在 2026 年 6 月移除了它,因为代理几乎从不执行它,而且审查和实现作为独立会话效果更好。重构属于 code-review。
垂直切片。 在一个接缝上编写一个测试,然后是最小实现,然后重复。第一个循环是一枚曳光弹,证明单条路径端到端可行。相反的是水平切片:先写所有测试,然后写所有代码。批量编写的测试验证的是想象中的行为。它们检查事物的形状,而不是用户实际做什么,并且在你理解实现之前就把你锁定在一种测试结构上。
预先同意的接缝。 接缝是你观察行为而不深入内部的公共边界。该规则没有例外。没有测试可以放在未确认的接缝上。在完整链路中,接缝更早地在 to-spec 期间确定:“/tdd 被告知只工作在预先同意的测试接缝上,/code-review 检查是否只使用了已同意的测试接缝。”单独调用时,tdd 会直接询问你。
它旨在防止的三种反模式:
| 反模式 | 迹象 |
|---|---|
| 与实现耦合 | 当你重命名内部函数时测试会失败,尽管行为没有改变。mock 内部协作者、断言调用次数、使用数据库查询来验证而不是接口。 |
| 同义反复 | 期望值以代码计算的方式计算,因此测试天然通过。期望值必须来自其他地方:已知正确的字面量、算例、规格。 |
| 水平切片 | 一批测试在任何实现之前落地。 |
Mock 仅用于系统边界:外部 API、时间、随机性,有时是文件系统或数据库。永远不要 mock 你自己的模块。
常见问题¶
它为什么不重构?描述里写着 “red-green-refactor”。
因为重构步骤被移除了,但描述没有。移除是故意的。代理几乎从不执行该步骤,而且将实现和审查保持在独立会话中效果更好。结果是否仍算教科书式 TDD,不如循环是否产生更好的代码重要。触发短语和正文之间的不一致已记录为 issue #589,目前仍处于开放状态,因此 “red-green-refactor” 继续作为触发该技能的短语有效。你得到的是红-绿,重构在 code-review 中。
它让我选择一个测试接缝,而我完全不知道该选哪个。
这是该技能被报告最多的摩擦点(issue #607)。提示只按名称列出候选接缝,没有说明每个接缝能捕获或遗漏什么,因此你是在标签之间选择。目前还没有发布修复。实用的变通方法是在回答前询问代理权衡:组件级接缝会遗漏集成接缝能捕获的什么,以及它慢多少。这也是为什么链路在 to-spec 中预先确定接缝,在那里你看到的是整个功能,而不是一个提示。
它先写了实现,再写测试,尽管该技能说要先红。
这种情况会发生。一位用户追问 模型,得到了一个异常诚实的回答:“我知道技能里写着‘一次一个测试,观察它因正确的原因失败’。我读到了。我只是默认回到了平时的习惯。”该技能接受这一点。没有任何指令能让代理 100% 地遵守,更严格的措辞只会限制代理的创造力,收益却很小。即使代理没有严格遵循,这个循环也值得运行,因为整体结果仍然更好。如果某个特定切片需要严格遵循,请观察运行过程,而不是指望技能来强制执行。
它应该先写浏览器测试还是端到端测试?
通常不应该,而且该技能也不会阻止它。有用户反馈代理先写了一个 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 坏味道基线。
它知道我的其他工单吗?
不会。针对单个工单运行时,它可能会提出属于兄弟工单的工作,因为它看不到 issue 图的其余部分(issue #129)。这不是 tdd 的职责。把规格说明和工单一起传入会有帮助;一开始就把工单规模定得合适,帮助更大。
如果它正在起作用¶
- 它会停下来,指出它打算测试的接缝,并在任何测试文件存在之前等待。
- 一个测试出现,变红,只加入刚好足够的代码使其通过,然后下一个测试才出现,而不是一批测试后跟着一批代码。
- 测试名称读起来像能力(“用户可以用有效购物车结账”),而不是内部实现(“checkout 调用 paymentService.process”)。
- 断言中的期望值是可以追溯到规格说明的字面量,而不是按代码计算方式重新计算的值。
- 重命名一个内部函数不会破坏测试套件中的任何东西。
- 模拟对象只出现在外部边界(支付 API、时钟)处,而不会围绕你自己的模块。
它在哪里适用¶
tdd 运行在主链的构建步骤内部;它本身不是一个独立步骤:
to-spec 事先约定测试接缝,implement 按工单驱动 tdd,code-review 之后检查是否只使用了约定的接缝,并负责 tdd 不再负责的重构。它的另一个邻居是 codebase-design,这是 tdd 使用的接缝和深模块词汇的共享来源。你也可以单独使用它,只要有一个具体行为要构建,并且没有完整的规格说明可用。当你不确定哪个技能适合你的情况时,ask-matt 会引导你。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 skills13/skills