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 --> A

3.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 阶段引入 maxRetriesbackoff、回调钩子——那是 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 行为 的角色:

  1. 防止「实现先行」:Agent 倾向快速产出完整实现;铁律要求每个行为从失败测试出发。
  2. 提供完成判据:与 verification-before-completion Skill 配合——没有 fresh 的测试输出,就不能声称「完成」或「已修复」。
  3. 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 参考,三条铁律:

  1. Never test mock behavior — 测 mock 存在性,不测真实组件行为
  2. Never add test-only methods to production classes — 清理逻辑放 test utilities
  3. 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。