بنقرة واحدة
test-driven-development
在实现任何功能或修复 bug 时使用,在编写实现代码之前
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
在实现任何功能或修复 bug 时使用,在编写实现代码之前
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
当你有一份书面实现计划需要在单独的会话中执行,并设有审查检查点时使用
在开始任何对话时使用——确立如何查找和使用技能,要求在任何响应(包括澄清性问题)之前调用 Skill 工具
在任何创造性工作之前必须使用此技能——创建功能、构建组件、添加功能或修改行为。在实现之前先探索用户意图、需求和设计。
当实现完成、所有测试通过、需要决定如何集成工作时使用——通过提供合并、PR 或清理等结构化选项来引导开发工作的收尾
当需要开始与当前工作区隔离的功能开发,或在执行实现计划之前使用——通过原生工具或 git worktree 回退机制确保隔离工作区存在
中文 review 沟通参考——话术模板、分级标注(必须修复/建议修改/仅供参考)、国内团队常见反模式应对。仅在用户显式 /chinese-code-review 时调用,不要根据上下文自动触发。
| name | test-driven-development |
| description | 在实现任何功能或修复 bug 时使用,在编写实现代码之前 |
| version | 1.0.0 |
| license | MIT |
| metadata | {"hermes":{"tags":["testing","development"]}} |
先写测试。看它失败。写最少的代码让它通过。
核心原则: 如果你没有看到测试失败,你就不知道它是否测试了正确的东西。
违反规则的字面意思就是违反规则的精神。
始终使用:
例外(需询问你的人类伙伴):
想着"就这一次跳过 TDD"?停下来。那是在给自己找借口。
没有失败的测试,就不写生产代码
先写了代码再写测试?删掉它。从头来过。
没有例外:
从测试出发,重新实现。句号。
digraph tdd_cycle {
rankdir=LR;
red [label="红灯\n编写失败的测试", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="验证正确失败", 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="保持\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
确认:
测试失败了? 修改代码,不是测试。
其他测试失败了? 立即修复。
只有在绿灯之后才重构:
保持测试绿灯。不要添加行为。
为下一个功能写下一个失败的测试。
| 特质 | 好的 | 差的 |
|---|---|---|
| 最小化 | 只测一件事。名称中有"和"?拆分它。 | test('validates email and domain and whitespace') |
| 清晰 | 名称描述行为 | test('test1') |
| 展示意图 | 展示期望的 API | 掩盖了代码应该做什么 |
"我先写完再补测试来验证"
后写的测试立即通过。立即通过什么也证明不了:
先写测试迫使你看到测试失败,证明它确实在测试某些东西。
"我已经手动测试了所有边界情况"
手动测试是临时的。你以为你测试了所有情况,但是:
自动化测试是系统性的。它们每次以相同方式运行。
"删除 X 小时的工作太浪费了"
沉没成本谬误。时间已经花了。你现在的选择:
"浪费"的是保留你无法信任的代码。没有真正测试的可运行代码就是技术债。
"TDD 太教条了,务实意味着灵活变通"
TDD 就是务实的:
"务实的"捷径 = 在生产环境调试 = 更慢。
"后补测试也能达到相同目的——重要的是精神不是仪式"
不对。后补测试回答"这段代码做了什么?"先写测试回答"这段代码应该做什么?"
后补测试受你实现的偏见影响。你测试的是你构建的东西,而非需求要求的。你验证的是你记得的边界情况,而非发现的。
先写测试迫使你在实现前发现边界情况。后补测试验证的是你记住了所有情况(你没有)。
30 分钟的后补测试 ≠ TDD。你得到了覆盖率,但失去了测试有效的证明。
| 借口 | 现实 |
|---|---|
| "太简单了不用测" | 简单的代码也会出 bug。测试只需 30 秒。 |
| "我之后补测试" | 立即通过的测试什么也证明不了。 |
| "后补测试也能达到相同目的" | 后补测试 = "这做了什么?" 先写测试 = "这应该做什么?" |
| "已经手动测试过了" | 临时测试 ≠ 系统测试。无记录,无法重现。 |
| "删除 X 小时的工作太浪费" | 沉没成本谬误。保留未验证的代码就是技术债。 |
| "留作参考,然后先写测试" | 你会去改编它。那就是后补测试。删除就是删除。 |
| "需要先探索一下" | 可以。探索完了扔掉,从 TDD 开始。 |
| "测试难写 = 设计不清楚" | 听测试的。难以测试 = 难以使用。 |
| "TDD 会拖慢我" | TDD 比调试快。务实 = 先写测试。 |
| "手动测试更快" | 手动测试无法证明边界情况。每次修改你都得重新测。 |
| "现有代码没有测试" | 你在改进它。为现有代码补测试。 |
以上所有情况都意味着:删除代码。用 TDD 从头开始。
Bug: 空邮箱被接受了
红灯
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
验证红灯
$ npm test
FAIL: expected 'Email required', got undefined
绿灯
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
验证绿灯
$ npm test
PASS
重构 如果需要,提取验证逻辑以支持多个字段。
在标记工作完成之前:
不能全部勾选?你跳过了 TDD。从头开始。
| 问题 | 解决方案 |
|---|---|
| 不知道怎么测试 | 写出你期望的 API。先写断言。问你的人类伙伴。 |
| 测试太复杂 | 设计太复杂。简化接口。 |
| 必须 mock 所有东西 | 代码耦合太紧。使用依赖注入。 |
| 测试 setup 太庞大 | 提取辅助函数。还是复杂?简化设计。 |
发现 bug?写一个重现 bug 的失败测试。按 TDD 循环走。测试既证明了修复有效,又防止了回归。
绝不在没有测试的情况下修复 bug。
添加 mock 或测试工具时,阅读 @testing-anti-patterns.md 以避免常见陷阱:
生产代码 → 测试存在且先失败
否则 → 不是 TDD
没有你的人类伙伴的许可,没有例外。