AGENT SKILL

测试驱动开发

实现任何功能或 bug 修复时,在编写实现代码前使用 (触发词:TDD)

适用职业开发工程师
使用场景测试与质量
License 许可
Apache-2.0
最近更新
2026-10-11
Stars
★ 0
Forks
0

01 前言 Front Matter

name
测试驱动开发
description
实现任何功能或 bug 修复时,在编写实现代码前使用 (触发词:TDD)
trigger_words
[ "@测试驱动开发", "@tdd", "@TDD" ]

02 资源文件 1

03 SKILL.md 正文

测试驱动开发(TDD)

概览

先写测试。看它失败。再写最少的代码让它通过。

核心原则: 如果你没有看见测试失败,就不知道它是否测试了正确的东西。

违反规则的字面要求,就是违反规则精神。

何时使用

始终使用:

  • 新功能
  • Bug 修复
  • 重构
  • 行为变更

例外(询问你的协作者):

  • 一次性原型
  • 生成代码
  • 配置文件

如果你在想“这次就跳过 TDD”,停下。这是合理化借口。

铁律

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

在测试前写了代码?删除它。重新开始。

没有例外:

  • 不要把它保留为“参考”
  • 不要在写测试时“改造”它
  • 不要看它
  • 删除就是删除

从测试重新实现。就是这样。

红-绿-重构

digraph tdd_cycle {
    rankdir=LR;
    red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
    verify_red [label="Verify fails\ncorrectly", shape=diamond];
    green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
    verify_green [label="Verify passes\nAll green", shape=diamond];
    refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
    next [label="Next", shape=ellipse];

    red -> verify_red;
    verify_red -> green [label="yes"];
    verify_red -> red [label="wrong\nfailure"];
    green -> verify_green;
    verify_green -> refactor [label="yes"];
    verify_green -> green [label="no"];
    refactor -> verify_green [label="stay\ngreen"];
    verify_green -> next;
    next -> red;
}

RED:编写失败测试

写一个最小测试,展示应该发生什么。

<Good>

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);
});

名称清楚,测试真实行为,只测一件事 </Good>

<Bad>

test('retry works', async () => {
  const mock = jest.fn()
    .mockRejectedValueOnce(new Error())
    .mockRejectedValueOnce(new Error())
    .mockResolvedValueOnce('success');
  await retryOperation(mock);
  expect(mock).toHaveBeenCalledTimes(3);
});

名称含糊,测试的是 mock 而不是代码 </Bad>

要求:

  • 一个行为
  • 名称清晰
  • 真实代码(除非不可避免,否则不用 mock)

验证 RED:看它失败

强制执行。绝不要跳过。

# Go 单文件改动
./scripts/test_with_guard.sh path/to/file.go

# Go 包改动
./scripts/test_with_guard.sh ./affected/package -count=1

# frontend-app 改动
cd frontend-app && npm test -- path/to/test.test.ts

确认:

  • 测试失败(不是报错)
  • 失败消息符合预期
  • 因功能缺失而失败(不是拼写错误)

测试通过? 你在测试已有行为。修正测试。

测试报错? 修复错误,重新运行,直到它正确失败。

GREEN:最小代码

写最简单的代码让测试通过。

<Good>

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');
}

刚好足够通过 </Good>

<Bad>

async function retryOperation<T>(
  fn: () => Promise<T>,
  options?: {
    maxRetries?: number;
    backoff?: 'linear' | 'exponential';
    onRetry?: (attempt: number) => void;
  }
): Promise<T> {
  // YAGNI
}

过度设计 </Bad>

不要添加功能、重构其他代码,或做超出测试要求的“改进”。

验证 GREEN:看它通过

强制执行。

npm test path/to/test.test.ts

确认:

  • 测试通过
  • 其他测试仍通过
  • 输出干净(没有错误、警告)

测试失败? 修代码,不修测试。

其他测试失败? 现在就修。

REFACTOR:清理

只有在 green 之后:

  • 移除重复
  • 改进命名
  • 提取 helper

保持测试为绿。不要添加行为。

重复

为下一个功能写下一个失败测试。

好测试

质量 好 坏
最小 一件事。名称里有 “and”?拆开。 test('validates email and domain and whitespace')
清晰 名称描述行为 test('test1')
展示意图 展示期望 API 掩盖代码应该做什么

为什么顺序重要

“我之后再写测试来验证它能工作”

代码之后写的测试会立即通过。立即通过什么也证明不了:

  • 可能测试了错误内容
  • 可能测试实现而不是行为
  • 可能漏掉你忘记的边界情况
  • 你从未看见它抓住 bug

测试先行迫使你看到测试失败,证明它确实测试了某些东西。

“我已经手动测试了所有边界情况”

手动测试是临时的。你以为自己测了所有内容,但:

  • 没有测试记录
  • 代码变更时无法重跑
  • 压力下很容易忘记情况
  • “我试的时候能用” ≠ 全面

自动化测试是系统化的。它每次都以同样方式运行。

“删除 X 小时工作是浪费”

沉没成本谬误。时间已经花掉了。现在的选择是:

  • 删除并用 TDD 重写(再花 X 小时,高置信度)
  • 保留它并事后加测试(30 分钟,低置信度,很可能有 bug)

真正的“浪费”是保留你不能信任的代码。没有真实测试的可工作代码就是技术债。

“TDD 太教条,务实意味着要适配”

TDD 就是务实:

  • 提交前发现 bug(比事后调试更快)
  • 防止回归(测试立即捕获破坏)
  • 记录行为(测试展示如何使用代码)
  • 支持重构(放心修改,测试会捕获破坏)

“务实”捷径 = 在生产中调试 = 更慢。

“事后测试也能达到同样目标,这重在精神而不是仪式”

不。事后测试回答“它做了什么?”测试先行回答“它应该做什么?”

事后测试会被你的实现影响。你测试的是你构建了什么,而不是需求是什么。你验证的是记得的边界情况,不是先发现的边界情况。

测试先行迫使你在实现前发现边界情况。事后测试只是验证你是否记住了一切(你不会)。

30 分钟事后测试 ≠ TDD。你获得覆盖率,但失去测试有效性的证明。

常见合理化借口

借口 现实
“太简单,不用测” 简单代码也会坏。测试只要 30 秒。
“我之后再测” 立即通过的测试什么也证明不了。
“事后测试也能达到同样目标” 事后测试 = “这做了什么?” 测试先行 = “这应该做什么?”
“已经手动测过” 临时测试 ≠ 系统化。没有记录,无法重跑。
“删除 X 小时太浪费” 沉没成本谬误。保留未验证代码是技术债。
“保留作参考,先写测试” 你会改造它。这就是事后测试。删除就是删除。
“需要先探索” 可以。丢弃探索结果,然后从 TDD 开始。
“测试很难 = 设计不清” 听测试的。难测试 = 难使用。
“TDD 会拖慢我” TDD 比调试快。务实 = 测试先行。
“手动测试更快” 手动无法证明边界情况。每次变更都要重测。
“现有代码没有测试” 你正在改进它。为现有代码补测试。

红旗:停止并重新开始

  • 先写代码后写测试
  • 实现后补测试
  • 测试立即通过
  • 说不清测试为什么失败
  • “稍后”添加测试
  • 为“这次例外”找理由
  • “我已经手动测过”
  • “事后测试也能达到同样目的”
  • “重在精神,不是仪式”
  • “保留作参考” 或 “改造现有代码”
  • “已经花了 X 小时,删除太浪费”
  • “TDD 太教条,我很务实”
  • “这次不一样,因为……”

这些都意味着:删除代码。从 TDD 重新开始。

示例:Bug 修复

Bug: 空 email 被接受

RED

test('rejects empty email', async () => {
  const result = await submitForm({ email: '' });
  expect(result.error).toBe('Email required');
});

验证 RED

$ npm test
FAIL: expected 'Email required', got undefined

GREEN

function submitForm(data: FormData) {
  if (!data.email?.trim()) {
    return { error: 'Email required' };
  }
  // ...
}

验证 GREEN

$ npm test
PASS

REFACTOR 必要时提取多字段验证。

验证检查清单

标记工作完成前:

  • [ ] 每个新函数/方法都有测试
  • [ ] 实现前看过每个测试失败
  • [ ] 每个测试都因预期原因失败(功能缺失,而不是拼写错误)
  • [ ] 写了最小代码让每个测试通过
  • [ ] 所有测试通过
  • [ ] 输出干净(没有错误、警告)
  • [ ] 测试使用真实代码(仅在不可避免时使用 mock)
  • [ ] 覆盖边界情况和错误

不能勾选所有项?你跳过了 TDD。重新开始。

卡住时

问题 解决方案
不知道如何测试 写理想 API。先写断言。询问你的协作者。
测试太复杂 设计太复杂。简化接口。
必须 mock 一切 代码耦合过高。使用依赖注入。
测试设置巨大 提取 helper。仍复杂?简化设计。

调试集成

发现 bug?写一个失败测试复现它。遵循 TDD 循环。测试证明修复并防止回归。

绝不要在没有测试的情况下修 bug。

测试反模式

添加 mock 或测试工具时,读取 @testing-anti-patterns.md,避免常见陷阱:

  • 测试 mock 行为而不是真实行为
  • 向生产类添加仅供测试使用的方法
  • 在不理解依赖的情况下 mock

最终规则

Production code → test exists and failed first
Otherwise → not TDD

除非你的协作者允许,否则没有例外。

04 同仓库更多 Skills

lihah111222333-cloud
lihah111222333-cloud/Super-Dolphin-Agent-Cockpit

mcp-lsp-project-config

配置、安装、检查、修复或迁移项目级 mcp-lsp stdio MCP,覆盖 Codex、Claude Code 和 Google Antigravity,以及 Windows 原生、WSL 原生和 Windows 宿主通过 wsl.exe 桥接 Linux sidecar 的自动选型。用户提到项目 MCP、LSP MCP、.codex/config.toml、.mcp.json、.agents/mcp_config.json、Codex 中 LSP 工具有时直接暴露有时只在延迟工具目录、ALL_TOOLS 过滤为空、工具已显示但调用报 client is closing、EOF、Transport closed,或希望三个客户端共用仓库内 mcp-lsp 时使用。

Skill与智能体开发
★ 0
lihah111222333-cloud
lihah111222333-cloud/Super-Dolphin-Agent-Cockpit

注释规范

当在 super-agent-v3 中编写或审查 Go 函数级中文注释、React hooks/service/controller 注释,或处理注释守卫失败时使用。

★ 0
lihah111222333-cloud
lihah111222333-cloud/Super-Dolphin-Agent-Cockpit

字段守卫

当新增、删除、重命名或跨层传递结构化字段,以及修改 DTO、mapper、schema、API/RPC、事件、配置、store、序列化或前后端契约时自动使用;追踪生产源到全部消费端并建立 fail-fast 字段守卫。

后端与接口
★ 0
lihah111222333-cloud
lihah111222333-cloud/Super-Dolphin-Agent-Cockpit

后端

在 super-agent-v3 中编写、审查或重构 Go 后端代码时使用。

后端与接口Skill与智能体开发
★ 0
lihah111222333-cloud
lihah111222333-cloud/Super-Dolphin-Agent-Cockpit

前端

当在 super-agent-v3 中开发、审查或修复当前 React/Vite 前端 UI 时使用;前端源码只在 frontend-app。

前端开发
★ 0
lihah111222333-cloud
lihah111222333-cloud/Super-Dolphin-Agent-Cockpit

全量项目地图生成

当在 super-agent-v3 中生成、更新或校验 docs/doc/codemap 项目地图、AI index 或能力清单时使用。

★ 0