1 问题背景
TDD(Test-Driven Development)在工程团队里并不新鲜:先写测试、看失败、写最少实现、再重构。但 AI 辅助编码改变了这条链路的执行方式——Agent 可以在几分钟内产出大量「看起来能跑」的实现,却未必真正验证过测试是否有效。
Superpowers 的 TDD Skill 针对的正是这个缺口:它不是把 TDD 当成可选建议,而是把 Red-Green-Refactor 写成 Agent 必须遵守的工作流约束。核心判断是:如果你没有亲眼看到测试失败,就无法确认它在测正确的东西。
2 核心结论
Superpowers TDD 的铁律只有一条:
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST 没有先失败的测试,就不写生产代码。
违反字面规则,就是在违反精神规则。先写了实现再补测试?删掉,重来。想保留代码「作参考」边写测试边改?那仍然是 test-after,不算 TDD。
对 AI Agent 而言,这条铁律的意义在于:把「测试通过」从结果断言,变成 可验证的过程证据——每个新行为都必须经历完整的 Red-Green 循环,而不是在实现完成后一次性补覆盖。
3 Red-Green-Refactor 循环
Superpowers TDD 把经典 TDD 拆成五个强制步骤,其中两个「Verify」环节最容易被跳过,也最关键。
flowchart LR
A["RED\n写失败测试"] --> B{"Verify RED\n确认失败原因正确"}
B -->|是| C["GREEN\n写最少实现"]
B -->|否| A
C --> D{"Verify GREEN\n确认通过且无副作用"}
D -->|是| E["REFACTOR\n清理结构"]
D -->|否| C
E --> D
D --> F["下一个行为"]
F --> A3.1 RED:写一个失败测试
要求:一个行为、一个清晰命名、尽量测真实代码。
好的测试直接描述期望行为:
test('retries failed operations 3 times', async () => {
let attempts = 0;
const operation = () => {
attempts++;
if (attempts < 3) throw new Error('fail');
return 'success';
};
const result = await retryOperation(operation);
expect(result).toBe('success');
expect(attempts).toBe(3);
});
差的测试测的是 mock 行为,而不是被测逻辑:
test('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
命名里出现 and,通常意味着该拆成多个测试。测试名应回答「这段代码应该做什么」,而不是 test1。
3.2 Verify RED:强制观察失败
不可跳过。 运行测试后必须确认:
- 测试确实 fail,而不是 error(语法/配置问题)
- 失败信息符合预期(例如「功能缺失」,而非拼写错误)
- 失败原因指向「尚未实现的行为」
如果测试一上来就 pass,说明你在测已有行为,测试写错了。如果 error,先修到能正确 fail 为止。
3.3 GREEN:写最少实现
只写让当前测试通过的最小代码,不加额外功能、不顺手重构、不顺便优化。
async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
for (let i = 0; i < 3; i++) {
try {
return await fn();
} catch (e) {
if (i === 2) throw e;
}
}
throw new Error('unreachable');
}
不要在 GREEN 阶段引入 maxRetries、backoff、回调钩子——那是 YAGNI 的典型违反。
3.4 Verify GREEN:强制观察通过
同样 不可跳过。确认:
- 当前测试 pass
- 其他测试仍 pass
- 输出干净(无 error、无 warning)
当前测试 fail 时修实现,不要改测试去迎合错误实现。其他测试 fail 时立即处理,不要留到后面。
3.5 REFACTOR:只在绿色时重构
Green 之后才能去重、改名、提取 helper。重构不加新行为,且全程保持测试绿色。
4 为什么顺序不能颠倒
Superpowers TDD 对「先实现后补测」有明确反驳,因为两类测试回答的是不同问题:
| 顺序 | 测试在回答什么 |
|---|---|
| Test-first | 应该做什么 |
| Test-after | 已经做了什么 |
Test-after 的测试会立即 pass,而 立即 pass 证明不了任何事:可能测错对象、测了实现细节而非行为、漏掉边界条件,且你从未见过它捕获缺失功能的那一刻。
常见自我合理化及对应现实:
| 借口 | 现实 |
|---|---|
| 「太简单,不用测」 | 简单代码也会坏,写测试通常只需几十秒 |
| 「我手动测过了」 | 手动测试无记录、不可重复、压力下易遗漏 |
| 「删掉 X 小时工作是浪费」 | 沉没成本谬误;保留无法验证的代码才是技术债 |
| 「测完再加,精神一样」 | 30 分钟补测 ≠ TDD,你得到覆盖率,失去「测试有效」的证明 |
| 「先探索,保留代码作参考」 | 探索可以,但进入 TDD 前必须删除探索代码,从零开始 |
Superpowers 的立场是:TDD 本身就是 pragmatic 的——它在 commit 前发现 bug、防止回归、用测试文档化行为、为重构提供安全网。「灵活跳过」往往意味着把调试成本推到生产环境。
5 与 AI Agent 协作时的特殊价值
在 Cursor 等 AI 编码环境中,TDD Skill 还承担 约束 Agent 行为 的角色:
- 防止「实现先行」:Agent 倾向快速产出完整实现;铁律要求每个行为从失败测试出发。
- 提供完成判据:与
verification-before-completionSkill 配合——没有 fresh 的测试输出,就不能声称「完成」或「已修复」。 - Bug 修复同样走 TDD:发现 bug → 写复现失败的测试 → GREEN → 测试同时证明修复并防止回归。
Bug 修复示例:
// RED
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
// 预期 Verify RED 输出:
// FAIL: expected 'Email required', got undefined
// GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
6 测试反模式:TDD 帮你避开什么
Superpowers 附带 testing-anti-patterns.md 参考,三条铁律:
- Never test mock behavior — 测 mock 存在性,不测真实组件行为
- Never add test-only methods to production classes — 清理逻辑放 test utilities
- Never mock without understanding dependencies — 不理解副作用就 mock,容易 mock 掉测试依赖的行为
Mock 是隔离手段,不是被测对象。如果 TDD 流程正确——先对真实代码看 fail,再按需最小化 mock——通常不会滑向「测 mock 而非测代码」。
测试 setup 过长、mock 占比过半、去掉 mock 测试就挂,这些都是设计过耦合或 mock 层级选错的信号。此时应简化接口或考虑集成测试,而不是堆更多 mock。
7 完成前核查清单
Superpowers TDD 要求在声称完成前逐项确认:
- 每个新函数/方法都有对应测试
- 每个测试都 先 fail 再 pass
- 每次 fail 的原因符合预期(功能缺失,非 typo)
- 实现是 最少 通过当前测试的代码
- 全部测试 pass,输出无 warning/error
- 尽量使用真实代码,mock 仅在不可避免时使用
- 边界与错误路径已覆盖
任一项打不了勾,即视为跳过了 TDD,应从头来过。
8 适用边界
Superpowers TDD 要求 Always 的场景:新功能、Bug 修复、重构、行为变更。
例外需与 human partner 明确协商:
- 一次性原型(throwaway prototype)
- 生成代码(generated code)
- 纯配置文件
「就这一次跳过」是典型危险信号。Skill 的原文措辞很直接:违反字面规则,就是违反精神规则。
9 卡住时怎么办
| 问题 | 方向 |
|---|---|
| 不知道怎么测 | 先写期望 API,先写 assertion,或向 human partner 求助 |
| 测试太复杂 | 接口设计可能过复杂,先简化 |
| 什么都要 mock | 耦合过重,考虑依赖注入 |
| Setup 巨大 | 提取 helper;仍复杂则回到设计 |
测试难写,往往意味着代码难用。 测试是设计反馈,不是事后负担。
10 总结
Superpowers TDD 把 TDD 从团队口头约定,变成 Agent 可执行、可核查的工作流:先失败测试,再最少实现,两次强制验证,绿色后才重构。 它的价值不在于「多写测试」,而在于 每个测试都经历过 fail 的证明——你知道它在测什么,而不只是看到一行 green。
对 AI 辅助开发来说,这条约束尤其重要:Agent 可以快速生成大量代码,但快速不等于正确。铁律 Production code → test exists and failed first,否则就不是 TDD。
最终判据只有一句:
有生产代码,就必须有先失败的测试;否则,不算 TDD。