with one click
test-driven-development
在实现任何 feature 或 bugfix 时使用;在写实现代码之前必须先写测试并看到它失败(TDD)。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
在实现任何 feature 或 bugfix 时使用;在写实现代码之前必须先写测试并看到它失败(TDD)。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
初始化新 skill 目录(运行 init_skill.py)、打包 skill(运行 package_skill.py)、或需要了解 skill 结构规范时使用。
在编写 skill 内容、验证 skill 是否有效、或需要用 TDD 方法测试 skill 能否被正确遵守时使用。
Frontend Master - 大师级前端页面开发。智能分析项目技术栈,生成独特设计美感的 UI,避免'AI审美'。自动持久化设计规范,保持项目一致性。整合 Frontend-Design 设计哲学 + UI-UX Pro Max 设计数据库。触发词: 前端、页面、组件、UI、登录页、落地页、dashboard、表单、卡片、导航栏。
你必须在任何创意工作之前使用:新功能、组件搭建、添加能力或修改行为。先通过对话澄清用户意图、需求与设计,再进入实现。
当你面对 2 个以上彼此独立的任务(无共享状态、无顺序依赖)时使用:为每个独立问题域派发一个 agent 并行推进。
当你已经有一份书面的 implementation plan,需要在独立 session 中按批执行并在批次间做 review checkpoint 时使用。
| name | test-driven-development |
| description | 在实现任何 feature 或 bugfix 时使用;在写实现代码之前必须先写测试并看到它失败(TDD)。 |
先写测试。看它失败。再写最小代码让它通过。
核心原则: 如果你没有亲眼看过测试失败,你就不知道它是否真的在测试正确的东西。
只遵守字面、不遵守精神,就是在违反规则。
永远:
例外(必须问 human partner):
如果你脑子里出现“就这一次跳过 TDD 吧”——停。那是在自我合理化。
没有先失败的测试,就禁止写 production code
先写了代码再写测试?删掉。重来。
没有例外:
从测试重新实现。就这样。
digraph tdd_cycle {
rankdir=LR;
red [label="RED\n写一个会失败的测试", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="确认失败\n且失败原因正确", shape=diamond];
green [label="GREEN\n最小实现代码", shape=box, style=filled, fillcolor="#ccffcc"];
verify_green [label="确认通过\n全绿", shape=diamond];
refactor [label="REFACTOR\n清理与重构", shape=box, style=filled, fillcolor="#ccccff"];
next [label="Next", shape=ellipse];
red -> verify_red;
verify_red -> green [label="yes"];
verify_red -> red [label="失败原因\n不对"];
green -> verify_green;
verify_green -> refactor [label="yes"];
verify_green -> green [label="no"];
refactor -> verify_green [label="保持\n全绿"];
verify_green -> next;
next -> red;
}
写一个最小测试,明确“应该发生什么”。
```typescript test('失败时会重试 3 次', 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>
```typescript
test('重试能工作', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(3);
});
命名含糊、测的是 mock 而不是代码行为
要求:
必须做。永远不要跳过。
npm test path/to/test.test.ts
确认:
测试直接通过? 你在测试既有行为。修测试。
测试报错? 修报错,反复运行直到它“正确地失败”。
写刚好能让测试通过的最简单代码。
```typescript async function retryOperation(fn: () => Promise): Promise { for (let i = 0; i < 3; i++) { try { return await fn(); } catch (e) { if (i === 2) throw e; } } throw new Error('unreachable'); } ``` 刚好足够通过测试 ```typescript async function retryOperation( fn: () => Promise, options?: { maxRetries?: number; backoff?: 'linear' | 'exponential'; onRetry?: (attempt: number) => void; } ): Promise { // YAGNI } ``` 过度设计不要在这里加功能、重构别的代码、或“顺手优化”超出测试需求的东西。
必须做。
npm test path/to/test.test.ts
确认:
测试失败? 修代码,不要修测试。
其他测试失败? 现在就修。
只有在全绿之后才做:
保持 tests 全绿。不要引入新行为。
为下一个功能点写下一个 failing test。
| 维度 | 好 | 坏 |
|---|---|---|
| Minimal | 一次只测一件事;名字里出现 “and”?拆开 | test('校验邮箱和域名和空白') |
| Clear | 名字描述行为 | test('test1') |
| Shows intent | 展示期望 API/用法 | 把期望行为藏起来 |
“我先写实现,之后再补测试验证”
事后写的测试通常会立刻通过;“立刻通过”证明不了任何事:
test-first 强迫你看到测试先失败,从而证明它确实在测试某个东西。
“我已经手动测过所有边界条件了”
手测是临时的、随机的:
自动化测试是系统化的,每次都一样地运行。
“删掉 X 小时的工作太浪费了”
沉没成本谬误。时间已经花掉了。你现在的选择是:
真正的浪费是保留“你无法信任”的代码。没有真实测试的“能跑”只是技术债。
“TDD 太教条,务实就该灵活点”
TDD 才是务实:
所谓“务实捷径”= 线上 debug = 更慢。
“事后测试也能达到同样目的——重要的是精神不是仪式”
不行。事后测试回答“它现在做了什么”;test-first 回答“它应该做什么”。
事后测试会被你的实现带偏:你会测试你写出来的东西,而不是需求要求的东西。你只会验证你记得的边界条件,而不是在实现前被迫发现的边界条件。
30 分钟的事后补测 ≠ TDD:你可能得到覆盖率,但失去“测试确实能抓 bug”的证明。
| 借口 | 现实 |
|---|---|
| “太简单了不值得测” | 简单代码也会坏;写个测试 30 秒。 |
| “我之后再测” | 立刻通过的测试证明不了任何事。 |
| “事后测试也一样” | 事后:what does this do;事前:what should this do。 |
| “我已经手测过了” | 临时 ≠ 系统;无记录、不可复跑。 |
| “删掉 X 小时太浪费” | 沉没成本;保留未验证代码是技术债。 |
| “留着当参考,先写测试” | 你会忍不住照着改;那就是事后测试。删就是删。 |
| “我得先探索一下” | 可以。探索完就丢掉,从 TDD 开始。 |
| “难测说明测试麻烦” | 难测 = 设计不清晰/难用;听测试的。 |
| “TDD 会拖慢我” | TDD 比 debug 更快;务实 = test-first。 |
| “手测更快” | 手测无法证明边界条件;每次改动都要重复测。 |
| “旧代码本来就没测试” | 你是在改进它;从你改的地方开始补测试。 |
看到这些就意味着:删掉代码。用 TDD 重来。
Bug: 空邮箱被接受
RED
test('拒绝空邮箱', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('需要邮箱');
});
Verify RED
$ npm test
FAIL: expected '需要邮箱', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: '需要邮箱' };
}
// ...
}
Verify GREEN
$ npm test
PASS
REFACTOR 如需要,可抽出多字段共享的 validation。
在标记“完成”之前逐项检查:
任何一项打不了勾?你跳过了 TDD。重来。
| 问题 | 解决方式 |
|---|---|
| 不知道怎么测 | 先写“你希望的 API”,先写断言;问 human partner。 |
| 测试太复杂 | 设计太复杂。简化接口。 |
| 必须 mock 一切 | 代码耦合太深。引入 dependency injection。 |
| 测试 setup 巨大 | 抽 helper;仍复杂就简化设计。 |
发现 bug?先写能复现的 failing test,然后走 TDD cycle。测试既证明修复有效,也防止 regression。
永远不要在没有测试的情况下修 bug。
当你要引入 mocks 或 test utilities 时,先读 @testing-anti-patterns.md,避免常见陷阱:
Production code → 必须先存在且先失败过的测试
否则 → 不是 TDD
除非 human partner 明确许可,否则没有例外。