Agent 方法论框架 superpowers 的 TDD 技能拆解
本文基于 superpowers 仓库 commit 44c9b2d(2026-07-27)梳理,该项目仍在持续迭代,具体行为以仓库 https://github.com/obra/superpowers 最新代码与文档为准。
Agent 写测试最常见的翻车方式不是不写,而是写完之后从没看着它失败过。 这套流程真正被 superpowers 的 TDD 技能拿来当靶子的,就是这一句:If you didn't watch the test fail, you don't know if it tests the right thing.(没看着它失败,你就不知道它测的是不是对的东西)。这句话写在 skills/test-driven-development/SKILL.md 的 Overview 里,紧接着的一句是 Violating the letter of the rules is violating the spirit of the rules. —— 违反规则的字面,就是违反规则的精神。这两句连在一起,基本定义了整份文档的语气:它不打算跟你商量。
站内已经写过 AI 写测试怎么用 和 AI 生成的代码能不能上生产,那两篇讲的是通用判断与方法论;这一篇不重复它们,只看一个真实开源项目怎么把这套方法论落成 Agent 能逐步执行的条文——哪句话写在哪个文件里,Agent 读到之后被要求做什么动作。
一、它盯的是哪一种失败模式
用编码 Agent 干活的人对下面这几幕不会陌生:任务做完了,Agent 顺手补了几个测试,一跑全绿,报告”已完成,测试全部通过”。问题在于这几个测试从来没有失败过,你没有任何证据说明它们能拦住 bug。
SKILL.md 的 Common Rationalizations 表里,专门有一行回应”我等会儿再补测试”:测试写在实现之后会立刻通过,而立刻通过什么都证明不了——它可能测错了对象,可能测的是实现细节而不是行为,也可能漏掉了你当时就没想到的边界。同一张表里还有一行更狠,回应的是”事后补测试也能达到同样目的,重要的是精神不是仪式”:事后写的测试回答的是”这段代码做了什么”,先写的测试回答的是”这段代码应该做什么”,前者天然被你已经写好的代码带偏。
这不是一句口号,而是这份技能后面所有硬性动作的理由。理解了这一点,你才会明白为什么它要把”看着它失败”单独列成一个必须执行的步骤,而不是合并进”跑测试”。
二、它由哪几块组成,铁律写在哪一块
技能目录 skills/test-driven-development/ 下只有两个文件:SKILL.md 和 writing-good-tests.md。但它在整个工作流里的落点不止这两处,下面这张表是我按实际读到的文件整理的地图。
| 组成部分 | 它负责什么 | 对应仓库位置 | 你什么时候会碰到它 |
|---|---|---|---|
| 铁律与红绿重构主流程 | 规定没有失败的测试就不许写生产代码,并把循环拆成写测试、验证失败、最小实现、验证通过、重构五个动作 | skills/test-driven-development/SKILL.md | 每次开始实现一个功能或修一个 bug |
| 借口对照表与红旗清单 | 预先把”太简单了不用测""我已经手动测过了""TDD 太教条我很务实”这些说法列出来,逐条给出反驳 | SKILL.md 的 Common Rationalizations 与 Red Flags 两节 | Agent 或你自己开始找理由跳过测试时 |
| 好测试的两条原则与两个 Gate Function | 规定每个测试必须说得出它拦住的是哪种改动,并且必须跑真东西而不是断言 mock | skills/test-driven-development/writing-good-tests.md | 写测试、加 mock、加只有测试才用的辅助方法时 |
| 变异检查 | 交付前在脑子里把生产代码改坏,看是否至少有一个测试会挂 | writing-good-tests.md 的 The Mutation Check 一节 | 一个测试文件写完、准备说”做完了”之前 |
| 计划模板里的步骤切分 | 把写失败测试、跑一遍确认它挂、写最小实现、跑测试确认通过、提交,直接排成计划文档里的独立勾选步骤 | skills/writing-plans/SKILL.md | 让 Agent 按计划逐任务执行时 |
| 调试流程里的接入点 | 规定修 bug 之前必须先有一个复现问题的失败测试,并指向 TDD 技能 | skills/systematic-debugging/SKILL.md 的 Phase 4 | 排查线上问题、准备动手改代码时 |
| 完成前的证据要求 | 没有在当前这条消息里跑过验证命令,就不许宣称通过 | skills/verification-before-completion/SKILL.md | Agent 说”已修复""测试都过了”的那一刻 |
顺带一个能自己核对的细节:仓库 README.md 的技能清单把配套参考称作 testing anti-patterns reference,但技能目录里实际的文件名是 writing-good-tests.md,SKILL.md 中的链接也指向后者;旧文件名 testing-anti-patterns.md 只在 RELEASE-NOTES.md 的历史记录里还能查到。这类文档与实现的轻微不同步,在读任何开源项目时都值得顺手核一下,别只信 README。
那条铁律,和它为什么写成这种语气
SKILL.md 里有一节直接叫 The Iron Law,内容只有一行:
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
紧跟着的处置也没有余地:Write code before the test? Delete it. Start over. 而且它把常见的规避方式一并堵死——不许留着当”参考”,不许在写测试时”改一改”,不许看它,删就是删。文档原话是 Delete means delete.
对人类工程师,这句话读起来像洁癖;对 Agent,它恰恰是必要的。Agent 一旦看到已有实现,写出来的测试会精确地贴着那份实现的形状,把实现里的 bug 一起当成规格测下来。删掉再从测试重写,是唯一能保证测试独立于实现的做法。
例外只有三类,而且都要求先问人:一次性原型、生成的代码、配置文件。文档还预判了你会怎么绕:Thinking "skip TDD just this once"? Stop. That's rationalization.(想着”就这一次跳过”?停。那是在给自己找理由。)
这种写法本身是有工程含义的。给 Agent 读的规则和给人读的规范不一样:人会自己权衡,Agent 会顺着任何一个语义缝隙滑过去。所以这份文档几乎不留”视情况而定”的表述,条件分支都写成确定的判断。如果你自己在给团队写 Agent 规则,这一点比它的 TDD 内容更值得参考——具体怎么写规则文件,可以对照 Claude Code 的 Skills 机制 看它的加载与触发方式。
三、红绿重构被拆成了五个带验证的动作
多数人对 TDD 的印象是三步:红、绿、重构。这份技能拆成了五步,多出来的两步都是验证。
RED —— 写一个最小的失败测试。 要求三条:只测一个行为、名字清楚、跑真代码(除非实在避不开才用 mock)。文档给的反例是一个叫 test('retry works') 的用例,里面用 jest.fn() 串了三次返回,最后断言 expect(mock).toHaveBeenCalledTimes(3)——名字含糊,而且测的是 mock 不是代码。
Verify RED —— 看着它失败。 这一步标着 MANDATORY,要求确认三件事:测试是 fail 而不是 error;失败信息是你预期的那条;失败的原因是功能不存在,而不是拼写错误。两种异常情况有明确处置:测试直接通过了,说明你测的是已有行为,该改的是测试;测试报 error,先修 error 再重跑,直到它以正确的方式失败。
GREEN —— 写刚好能过的代码。 反例是给 retryOperation 加上 maxRetries、backoff、onRetry 三个可选参数,而函数体里一行实现都没有,只留了一句注释 // YAGNI,代码块下面的批注是 Over-engineered。这一步明确禁止顺手加功能、顺手重构别处、顺手”改进”到测试要求之外。
Verify GREEN —— 看着它通过。 同样标 MANDATORY,确认三件事:这个测试过了、其他测试还是过的、输出干净(Output pristine,没有报错也没有警告)。这里有一条方向性的规定值得单独记:Test fails? Fix code, not test.——测试没过就改代码,不是改测试。这条和上一步”测试直接通过就改测试”合起来,才构成完整的判断。
REFACTOR —— 只在绿灯之后清理。 去重复、改名字、抽辅助函数,保持绿灯,不加行为。
文档末尾还有一张 When Stuck 表,把卡住的情形直接映射到设计问题上:不知道怎么测,就先写你希望的 API 或先写断言;测试太复杂,是设计太复杂;什么都得 mock,是代码耦合太重,该用依赖注入;测试的 setup 太大,先抽辅助函数,还是复杂就简化设计。它把”测试难写”当成设计反馈信号,而不是当成测试本身的麻烦。
四、什么算好测试:两条原则、两道闸、一次变异检查
writing-good-tests.md 是这套技能里更值得反复读的一份,它管的是”测试写出来了,但等于没写”这一类问题。开头两条原则很短:每个测试都要说得出它拦住的是什么;每个测试都要跑真东西。
原则一:说出它拦住的断点。 动笔写测试体之前先回答一个问题——什么样的生产代码改动会让这个测试挂,而那个改动是 bug 还是一个有意的决定?如果只有有意的决定才能让它失败,那它就是个变更探测器:每次重构都炸,真出 bug 时反而睡着。文档给的对照很具体:不要写 expect(MAX_RETRIES).toBe(5),要写”一次失败的调用被重试 5 次,第 6 次不会发生”。
同一节还点了两个高频错误。一个是镜像断言,期望值用被测代码自己算出来,那这个断言无论代码干什么都成立:
// ❌ Mirror assertion: the same builder computes both sides — always true
const expected = buildSearchQuery({ tag: 'urgent' });
expect(buildSearchQuery({ tag: 'urgent' })).toBe(expected);
// ✅ Hand-derived literal
expect(buildSearchQuery({ tag: 'urgent' })).toBe('tag:"urgent"');
另一个是拿源码文本当测试对象:断言某个脚本、技能文件或配置里包含某一行,只能证明”源码就是源码”。正确做法是把它跑起来,用受控输入去断言输出、副作用或退出码;而给 Agent 读的指令文档,要通过消费它的那个 Agent 的行为来验证。这一节还划了一条边界——测你自己代码的契约,别去测框架的既定机制,典型反例是断言你的路由器会调用你注册的处理函数,那是框架维护者该写的测试。
这一条对应的 Gate Function 是一段可以直接贴进团队规范的判断流程:
BEFORE writing the test body:
Name the production change that would make this test fail.
Cannot name one → redesign around an observable behavior
"The source text changed" → run the artifact and assert its effects
Only intentional decisions → change detector; test the behavior
that depends on the decision
Confirm the expected value is derived without the code under test.
IF it reuses the code's logic or helpers:
Replace it with a literal or hand-checked fixture
原则二:跑真东西。 核心一句是 The mock earns no assertions.——mock 不配拥有断言。一个断言在 mock 存在时通过、mock 拿掉时失败,它对被测组件什么都没说。文档把这条记成了一句人类协作者的追问:Are we testing the behavior of a mock?
围绕 mock,这份文档还有几条很实用的规定:在替换一个真实方法之前,先把它的副作用列全,只 mock 慢的或外部的那一层,测试依赖的部分保持为真;mock 的返回结构要按现实里的完整结构来写,只写测试读到的那几个字段,会在下游读到被省略字段时静默失败——测试绿着,集成断着;只有测试会调用的清理方法要放进测试工具里,不能变成生产类上的一个 destroy();当 mock 的搭建代码超过测试逻辑本身、或者真实组件有 mock 没实现的方法时,换成用真实组件的集成测试。
变异检查。 收尾前在脑子里把生产代码改坏,每一种现实的改法都应该至少挂掉一个测试:改错常量或参数、走错分支、漏掉状态变更或副作用、返回空值或默认值、缺少对零值/空值/nil/未授权/畸形输入的校验。没有任何测试能捕捉的那个变异,标记的是一块没被保护的行为,或者一个恒真的测试。这套判断放到 Agent 场景里格外有用——Agent 谎报成功 的一个典型形态就是产出一堆恒真断言然后宣布覆盖完成,变异检查是少数几个能当场戳穿它的手段。
五、边界与代价:它放弃了什么
这套设计是有代价的,而且代价不小。
开发会变慢,Agent 会变啰嗦。 每个功能点要多跑两次测试命令,多产出两段可读的输出。对多轮对话的 Agent,这意味着更多轮次、更多上下文占用。收益要在改动累积到一定规模、开始需要放心重构的时候才兑现;如果这个模块你写完就不打算再动,这笔投入不一定划算。
对小改动是过度设计。 文档自己给出的例外只有三类,而且都要先问人:一次性原型、生成的代码、配置文件。改一个文案、调一个日志级别这类事,走完整个循环的仪式成本明显高于收益。值得说清的是,writing-good-tests.md 并不主张什么都测——它写着”简单代码和给人看的散文一份测试都不配”,构造函数、getter、常量和纯转发只有在做了校验、归一化、默认值、派生、约束或产生副作用时才值得单独测,否则应该去断言第一个消费者能看到的结果。所以”什么都测”是对这套规则的误读。
它对存量系统的答复很轻,成本却很重。 借口表里对”现有代码本来就没测试”的回应是一句 You're improving it. Add tests for existing code.。在一个几十万行、耦合严重的老系统里,这句话背后的工作量文档没有讨论。
它明确不管的事情。 不管测试框架怎么选,SKILL.md 里出现的 npm test 只是示例命令,而计划模板 skills/writing-plans/SKILL.md 的同一批步骤里示例又换成了 pytest,可见跑什么命令属于你自己填的空;不管覆盖率指标该定多少(相反,它把”为了覆盖率而存在的测试”列进了 Warning Signs);不管单元测试、集成测试、端到端测试怎么分层;不管 CI 怎么搭;也不管性能与压力测试。这些都要你自己在工程里接。
约束的载体是给 Agent 读的文档。 它是否被遵守,取决于 Agent 每一步是不是真的照做了。这大概也是 SKILL.md 结尾要放一份 Verification Checklist 的原因——八条勾选项,包括”看着每个测试失败过""每个测试是因为预期原因失败的""输出干净”,最后一句是:勾不满就是没做 TDD,重来。想让约束真正生效,还得配上 验收标准怎么定 这一层的人为把关。
六、上手与避坑清单
按踩坑概率从高到低排,每条都写清为什么会踩。
一、把”跑过测试”当成”看着它失败过”。 会踩,是因为 Agent 通常把写测试和写实现放在同一轮里做,最后统一跑一次,输出全绿就报告完成——中间那个红灯从来没出现过。避法是把 Verify RED 的那次输出当成交付物要过来看:确认是 fail 不是 error、失败信息符合预期、失败原因是功能缺失而不是拼写错。
二、测试第一次跑就通过了,还觉得是好事。 会踩,是因为绿灯在直觉上总是好消息。这份技能的判断相反:测试立刻通过说明你测的是已有行为,该修的是测试。它被列进了 Red Flags,处置是重来。
三、断言打在 mock 身上。 会踩,是因为 mock 好写、断言好过,React 测试里断言一个 sidebar-mock 的 testId 甚至看起来很规范。避法就是那句追问:我们是在测一个 mock 的行为吗?如果是,要么把它 unmock,要么把这条断言删掉。
四、期望值是用被测代码算出来的。 会踩,是因为复用现成的 builder 写起来最省事,而且它看着像在”避免硬编码”。避法是手写字面量或人工核对过的固定数据,表驱动测试配上字面量的 want 值是文档偏好的形态。
五、mock 的层级选错,把测试真正要看的副作用一起吞了。 文档给的实例是:mock 掉工具目录的发现与缓存方法,会连带把重复检测所依赖的配置写入一起吞掉,正确做法是只 mock 掉慢的服务启动那一层。避法是在 mock 之前把真实方法的副作用列一遍,不确定就先拿真实实现跑一次测试,看清楚到底发生了什么。
六、mock 的返回只写测试读到的那几个字段。 会踩,是因为按需最小化在别的场合是好习惯。这里它会让下游代码读到被省略的字段时静默出错——测试一直绿,集成时才炸。避法是按现实中的完整结构来构造。
七、把只有测试用得上的清理方法加到生产类上。 会踩,是因为写的时候确实是”顺手放最近的地方”。自查两个问题:这个方法是不是只有测试文件在调?这个类是不是真的拥有这份资源的生命周期?答错任何一个,就该挪到测试工具里。
收尾自检。 在你说”这个功能做完了”之前,回答三个问题:这个功能的每个测试,我是不是都见过它失败的那次输出?如果我把生产代码里的某个常量改错、某个分支走反,是不是至少有一个测试会挂?这些测试里有没有哪一条,是在断言一个 mock 存在?三个问题里有一个答不上来,SKILL.md 最后那条 Final Rule 已经替你判了:
Production code → test exists and failed first
Otherwise → not TDD
接下来该读哪个文件,取决于你的痛点在哪:如果问题是 Agent 根本不肯先写测试,读 SKILL.md 的 Common Rationalizations 和 Red Flags 两节,把里面的说法直接搬进你自己的规则文件;如果问题是测试写了一堆但拦不住 bug,读 writing-good-tests.md,重点在两个 Gate Function 和末尾那份 Warning Signs 清单;如果问题是 Agent 老是虚报完成,去看 skills/verification-before-completion/SKILL.md,那份文档的铁律是”没有新鲜的验证证据就不许宣称完成”。整个仓库是 MIT 许可证,Copyright (c) 2025 Jesse Vincent,条文可以直接摘用。
本文属于 superpowers 方法论专题(共 30 篇,含三篇与其它开源 Agent 项目的对照)。想看把资产铺满的另一种取向,见 ECC 开源 Agent 套件专题。