بنقرة واحدة
test-driven-development
在编写任何实现代码之前,无论是新增功能还是修复 Bug,都必须使用本技能
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
在编写任何实现代码之前,无论是新增功能还是修复 Bug,都必须使用本技能
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
在开展任何创造性工作之前必须使用:创建功能、构建组件、添加能力或修改行为。在动手实现前,先探索用户意图、需求并制定设计。
当同时面对 2 个及以上相互独立、无共享状态且无顺序依赖的任务时必须使用本技能
当需要在一个独立会话中执行已撰写的实现计划,并通过检查点进行复核时必须使用
当实现完成、所有测试通过且需要决定如何集成工作时必须使用——通过呈现结构化的本地合并、创建 PR 或清理工作树等选项,指导开发工作的收尾
当收到代码审查反馈、准备采纳建议前必须使用,尤其当反馈表述不清或技术上可疑时——要求技术严谨与验证,禁止表面附和或盲目执行
当完成任务、实现主要功能或在合并前需要验证工作是否符合要求时必须使用
| name | test-driven-development |
| description | 在编写任何实现代码之前,无论是新增功能还是修复 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;
}
编写一个最简测试,展示期望行为。
```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
确认:
测试失败? 修复代码,而非测试。
其他测试失败? 立即修复。
只有在绿灯之后:
保持测试通过。不要添加行为。
为下一个功能编写下一个失败测试。
| 质量 | 良好 | 不良 |
|---|---|---|
| Minimal | 只测一件事。名称里出现“and”?拆分它。 | test('validates email and domain and whitespace') |
| Clear | 名称描述行为 | test('test1') |
| Shows intent | 展示期望的 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');
});
Verify RED
$ npm test
FAIL: expected 'Email required', got undefined
GREEN
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Verify GREEN
$ npm test
PASS
REFACTOR 如有需要,为多个字段提取验证逻辑。
在标记工作完成之前:
不能全部勾选?你跳过了 TDD。重来。
| 问题 | 解决方法 |
|---|---|
| 不知道怎么测 | 写出期望的 API。先写断言。询问人类伙伴。 |
| 测试太复杂 | 设计太复杂。简化接口。 |
| 必须 mock 所有东西 | 代码耦合过高。使用依赖注入。 |
| 测试 setup 过于庞大 | 提取辅助函数。仍然复杂?简化设计。 |
发现 Bug?编写一个能复现它的失败测试。遵循 TDD 循环。测试既能证明修复有效,也能防止回归。
绝不在没有测试的情况下修复 Bug。
在添加 mock 或测试工具时,请阅读 testing-anti-patterns.md 以避免常见陷阱:
Production code → test exists and failed first
Otherwise → not TDD
未经人类伙伴允许,没有任何例外。