with one click
test-driven-development
当实现任何功能或修复任何 bug 时使用,在编写实现代码之前
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
当实现任何功能或修复任何 bug 时使用,在编写实现代码之前
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 - 创建功能、构建组件、添加功能或修改行为。在实现之前探索用户意图、需求和设计。
当面对 2 个以上可在无共享状态或顺序依赖下处理的独立任务时使用
当你有一个书面实现计划,需要在带有 review 检查点的独立会话中执行时使用
当实现完成、所有测试通过、且你需要决定如何集成工作时使用 - 通过为合并、PR 或清理呈现结构化选项来指导开发工作的完成
当收到代码审查反馈时使用,在实现建议之前,尤其是当反馈看似不清或在技术上存疑时 - 需要技术严谨性和验证,而非表演性附和或盲目实现
当完成任务、实现主要功能或合并之前使用,以验证工作满足需求
| name | test-driven-development |
| description | 当实现任何功能或修复任何 bug 时使用,在编写实现代码之前 |
先写测试。看它失败。写最少的代码让它通过。
核心原则: 如果你没有看到测试失败,你就不知道它是否测对了东西。
违反规则的字面意义,就是违反规则的精神。
始终:
例外(询问你的 human partner):
在想"就这一次跳过 TDD"?停下。那是自我合理化。
没有先写出失败的测试,就不写任何生产代码
先写代码再写测试?删掉它。从头来。
没有例外:
从测试出发重新实现。到此为止。
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="下一个", 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="保持\n绿色"];
verify_green -> next;
next -> red;
}
写一个最简测试,展示应当发生什么。
```typescript 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>
```typescript
test('retry works', 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
确认:
测试失败了? 修复代码,而不是测试。
其他测试失败了? 立即修复。
仅在绿色之后:
保持测试为绿色。不要添加行为。
下一个失败测试,用于下一个功能。
| 质量 | 好 | 坏 |
|---|---|---|
| 最小 | 只测一件事。名字里有"and"?拆开它。 | test('validates email and domain and whitespace') |
| 清晰 | 名字描述行为 | test('test1') |
| 体现意图 | 展示期望的 API | 掩盖了代码应当做什么 |
"我之后再写测试来验证它有效"
在代码之后写的测试会立即通过。立即通过什么也证明不了:
测试先行强迫你看到测试失败,证明它确实在测试某些东西。
"我已经手动测试了所有边界情况"
手动测试是随性的。你以为测试了一切,但是:
自动化测试是系统化的。它们每次都以相同方式运行。
"删掉 X 小时的工作是浪费"
沉没成本谬误。时间已经花掉了。你现在的选择是:
真正的"浪费"是保留你无法信任的代码。没有真实测试的可运行代码就是技术债。
"TDD 是教条主义,务实意味着要变通"
TDD 就是务实的:
"务实"的捷径 = 在生产环境调试 = 更慢。
"后置测试能达到同样目标——重要的是精神而不是仪式"
不。后置测试回答"这做了什么?"先行测试回答"这应当做什么?"
后置测试受你的实现偏见影响。你测试的是你构建的东西,而不是需求。你验证的是你记得的边界情况,而不是发现的边界情况。
先行测试强迫你在实现之前发现边界情况。后置测试验证你记住了一切(你并没有)。
30 分钟的后置测试 ≠ TDD。你得到了覆盖率,却失去了测试有效的证明。
| 借口 | 现实 |
|---|---|
| "太简单了,不需要测试" | 简单代码也会出错。测试只要 30 秒。 |
| "我之后再测" | 立即通过的测试什么都证明不了。 |
| "后置测试能达到同样目标" | 后置测试 = "这做了什么?" 先行测试 = "这应当做什么?" |
| "已经手动测试过了" | 随性 ≠ 系统化。无记录,无法重新运行。 |
| "删掉 X 小时是浪费" | 沉没成本谬误。保留未验证的代码才是技术债。 |
| "留作参考,先写测试" | 你会去改编它。那就是后置测试。删除就是删除。 |
| "需要先探索" | 可以。但扔掉探索成果,用 TDD 从头开始。 |
| "测试很难写 = 设计不清楚" | 倾听测试。难测试 = 难使用。 |
| "TDD 会拖慢我" | TDD 比调试更快。务实 = 测试先行。 |
| "手动测试更快" | 手动测试无法证明边界情况。每次变更都要重新测试。 |
| "现有代码没有测试" | 你在改进它。为现有代码添加测试。 |
以上所有都意味着:删除代码。用 TDD 从头来。
Bug: 空邮箱被接受
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 如有需要,为多个字段提取校验逻辑。
在标记工作完成之前:
无法勾选所有框?你跳过了 TDD。从头来。
| 问题 | 解决方案 |
|---|---|
| 不知道怎么测试 | 写下你期望的 API。先写断言。问你的 human partner。 |
| 测试太复杂 | 设计太复杂。简化接口。 |
| 必须 mock 一切 | 代码耦合太重。使用依赖注入。 |
| 测试设置庞大 | 提取辅助函数。仍然复杂?简化设计。 |
发现了 bug?写一个能复现它的失败测试。遵循 TDD 循环。测试证明了修复,并防止回归。
绝不写没有测试的 bug 修复。
当添加 mock 或测试工具时,阅读 testing-anti-patterns.md 以避免常见陷阱:
生产代码 → 测试存在且先失败
否则 → 不是 TDD
未经你的 human partner 许可,没有例外。