بنقرة واحدة
test-driven-development
在实现任何功能或修复 bug 之前使用,先于实现代码编写测试
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
在实现任何功能或修复 bug 之前使用,先于实现代码编写测试
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
在进行任何创造性工作前必须使用——创建功能、构建组件、添加新功能或修改行为。在实现之前先探索用户意图、需求和设计。
TypeScript 和 React 开发的编码标准与最佳实践。在编写或审查代码时激活。提供命名、类型、组件设计、Hook 提取、状态管理等规范。
在用户确认要提交代码变更时使用——安全检查 diff 中的敏感数据后执行 git commit
在面对 2 个以上互不依赖、无共享状态的独立任务时使用
当有已编写的实现计划需要在独立会话中执行时使用,包含评审检查点
在实现完成、测试通过后,引导决定如何集成工作——提供合并、PR 或清理的结构化选项
| name | test-driven-development |
| description | 在实现任何功能或修复 bug 之前使用,先于实现代码编写测试 |
先写测试。看它失败。写最少代码使它通过。
核心原则: 如果你没有看到测试失败,你就不知道它是否测了正确的东西。
违反规则的字面意思就是违反规则的精神。
始终:
例外(需用户同意):
想着"就这一次跳过 TDD"?停下来。那是合理化借口。
没有先写失败测试,不写生产代码
先写了代码再写测试?删掉它。重新开始。
没有例外:
从测试出发重新实现。句号。
digraph tdd_cycle {
rankdir=LR;
red [label="红\n写失败测试", shape=box, style=filled, fillcolor="#ffcccc"];
verify_red [label="验证正确\n失败", 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。你得到了覆盖率,但失去了测试有效的证明。
| 借口 | 现实 |
|---|---|
| "太简单不需要测试" | 简单代码也会出错。测试只要 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?写一个失败测试复现它。遵循 TDD 循环。测试证明修复有效并防止回归。
不写测试就不修 bug。
添加 mock 或测试工具时,阅读 @testing-anti-patterns.md 避免常见陷阱:
生产代码 → 测试存在且先失败了
否则 → 不是 TDD
没有用户的许可不得例外。