Agent Skills: 测试驱动开发(TDD)

>-

UncategorizedID: FlameMida/spec-dev/test-driven-development

Repository

FlameMidaLicense: NOASSERTION
12

Install this agent skill to your local

pnpm dlx add-skill https://github.com/FlameMida/spec-dev/tree/HEAD/skills/test-driven-development

Skill Files

Browse the full folder contents for test-driven-development.

Download Skill

Loading file tree…

skills/test-driven-development/SKILL.md

Skill Metadata

Name
test-driven-development
Description
>-

Language Protocol / 语言协议: Respond in the user's conversation language — an explicit user instruction (including the platform language setting) takes precedence, then the language of the user's recent messages; default to English when neither indicates a language. All deliverables written to the repo (specs, plans, reports, notes) follow the conversation language at creation; incremental edits keep the artifact's existing language. Fixed-wording prompts in this skill are semantic templates — express their meaning in the conversation language, don't quote them verbatim. 语言协议:以对话语言输出——用户显式指定(含平台 language 设置)优先,其次跟随用户近期消息语言;均无法判定时默认英语。落盘产物以创建时对话语言为准,增量修改保持产物既有语言。本 skill 中的固定话术是语义模板,用对话语言表达其意,不逐字照搬。

测试驱动开发(TDD)

概述

先写测试。看它失败。写最小代码让它通过。

核心原则:没看着测试失败过,你就不知道它测的是不是对的东西。

本 skill 只依赖项目自身的测试命令,不依赖任何平台专属工具,Claude Code 与 Codex 通用。

违反规则的字面 = 违反规则的精神。

何时使用

总是:新功能、bug 修复、重构、行为变更。

例外(需征得用户同意):一次性原型、生成代码、配置文件。

想着"就这一次跳过 TDD"?停。那是合理化借口。

铁律

没有失败测试,就没有生产代码

先写了代码?删掉,重来。没有例外:不留作"参考"、不"边写测试边改造它"、不看它。删除就是删除,从测试出发重新实现。

红-绿-重构

digraph tdd_cycle {
    rankdir=LR;
    red [label="红\n写失败测试", shape=box, style=filled, fillcolor="#ffcccc"];
    verify_red [label="确认失败\n原因正确", shape=diamond];
    green [label="绿\n最小实现", shape=box, style=filled, fillcolor="#ccffcc"];
    verify_green [label="确认通过\n全绿", shape=diamond];
    refactor [label="重构\n清理", shape=box, style=filled, fillcolor="#ccccff"];
    next [label="下一个", shape=ellipse];

    red -> verify_red;
    verify_red -> green [label="是"];
    verify_red -> red [label="失败原因\n不对"];
    green -> verify_green;
    verify_green -> refactor [label="是"];
    verify_green -> green [label="否"];
    refactor -> verify_green [label="保持绿"];
    verify_green -> next;
    next -> red;
}

红——写失败测试

写一个最小测试,表达应该发生什么:一个行为、清晰命名、测真实代码(万不得已才 mock)。

// ✅ 好:名字表意、测真实行为、只测一件事
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);
});

确认红——看它失败

强制步骤,永不跳过。 运行测试,确认:失败而非报错、失败信息符合预期、失败原因是功能缺失(不是拼写错误)。

  • 测试直接通过了? 你在测既有行为,改测试。
  • 测试报错了? 修到它以正确原因失败为止。

绿——最小实现

写让测试通过的最简代码。不加多余功能、不顺手重构别的代码、不超出测试要求"改进"。

// ✅ 好:刚好够通过
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');
}

// ❌ 坏:maxRetries/backoff/onRetry 没人要 —— YAGNI

确认绿——看它通过

强制步骤。 运行测试,确认:目标测试通过、其他测试仍通过、输出干净(无报错无警告)。

  • 测试失败? 修代码,不是修测试。
  • 其他测试挂了? 现在就修。

重构——清理

只在全绿后:去重、改名、提取辅助函数。保持全绿,不加行为。

重复

为下一个行为写下一个失败测试。

为什么顺序重要(借口对照表)

| 借口 | 现实 | |------|------| | "太简单不用测" | 简单代码也会坏。测试 30 秒的事。 | | "我写完再补测试" | 事后测试立即通过,什么都证明不了——可能测错对象、测实现不测行为、漏掉你忘了的边缘情况。 | | "事后测试达到同样目的" | 事后测试回答"这代码做了什么";事前测试回答"这代码该做什么"。事后测试被实现偏置——你测你建的,不是需求要的。 | | "我已经手工测过了" | 手工测试无记录、不可重跑、高压下必漏。"我试了没问题" ≠ 系统性验证。 | | "删掉 X 小时的工作太浪费" | 沉没成本谬误。留着没有真实测试的代码才是技术债。 | | "留着当参考,先写测试" | 你会照着改。那就是事后测试。删除就是删除。 | | "需要先探索" | 可以。探索完扔掉,从 TDD 重新开始。 | | "测试难写 = 该 mock 更多" | 测试难写 = 设计有问题。难测即难用。 | | "TDD 教条,我要务实" | TDD 就是务实:提交前抓 bug 比事后调试快,回归即刻暴露,测试即文档,重构有保护网。"务实的捷径" = 在生产环境调试 = 更慢。 |

Red Flags —— 停下来重来

  • 代码先于测试;测试后补;测试立即通过
  • 说不清测试为什么失败
  • "就这一次"、"我已经手工测过了"、"事后测试目的一样"、"重要的是精神不是仪式"
  • "留作参考"、"改造现有代码"、"已经花了 X 小时删了可惜"
  • "TDD 教条,我在务实"、"这次情况特殊因为……"

以上任何一条出现 = 删代码,从 TDD 重来。

完成前检查清单

  • [ ] 每个新函数/方法都有测试
  • [ ] 每个测试都看着它失败过
  • [ ] 每次失败原因正确(功能缺失,不是拼写错误)
  • [ ] 每个测试都用最小实现通过
  • [ ] 全部测试通过
  • [ ] 输出干净(无报错、无警告)
  • [ ] 测试用真实代码(mock 仅在万不得已时)
  • [ ] 边缘情况与错误路径已覆盖

勾不满?你跳过了 TDD。重来。

调试集成

发现 bug?先写复现它的失败测试,再走 TDD 循环。测试既证明修复又防止回归。永远不要不带测试修 bug。

Mock 与测试反模式

添加 mock、测试工具方法、或想给生产类加测试专用方法时,先读 testing-anti-patterns.md:不测 mock 的行为、不给生产类加测试专用方法、不在不理解依赖链时 mock、mock 必须镜像真实结构的完整字段。

最终规则

生产代码 → 存在先失败过的测试
否则 → 不是 TDD

未经用户允许,没有例外。