Programming · Testing
test-driven-development
/test-driven-development
此技能应在实现任何功能或修复 bug 时使用,在编写实现代码之前。 它强制执行「红-绿-重构」循环,确保代码有测试覆盖且行为正确。
For whom
- Author
- jerrylalala ↗
- Repository
- compound-engineering ↗
- Licence
- MIT
- Last repository change
- 01.06.2026
Assessment and security
96/ 100Class: good
Format25 / 25
Frontmatter with name and description, valid name, description up to 1,024 characters, name matching the folder.
Discoverability21 / 25
Description explains when to use the skill, has a reasonable length and uses third person.
Content35 / 35
At least 150 words and at most 500 lines, headings, step lists, examples or code.
Hygiene15 / 15
No placeholders such as TODO, no Windows paths, with a licence.
Riskno signals detected in the scan
- Files in this package
- 2
- Scripts
- 0
- HTML comments
- 0
- URLs in SKILL.md
- 0
Deprecation
No signals detected. Repository changed on 01.06.2026, without references to retired models or APIs.
Licence
MIT, open: use and modify while retaining author attribution.
What it does and when to use it
此技能应在实现任何功能或修复 bug 时使用,在编写实现代码之前。 它强制执行「红-绿-重构」循环,确保代码有测试覆盖且行为正确。
Original author description
此技能应在实现任何功能或修复 bug 时使用,在编写实现代码之前。 它强制执行「红-绿-重构」循环,确保代码有测试覆盖且行为正确。
How it works
- 铁律
- 何时使用
- 红-绿-重构循环
- 好测试的特征
- 为什么顺序重要
- 常见合理化借口
- 当卡住时
- 危险信号 - 停下来重新开始
- 示例:Bug 修复
- 验证清单
Original SKILL.md headings · 333 lines, 615 words
SKILL.md preview
---
name: test-driven-development
description: |
此技能应在实现任何功能或修复 bug 时使用,在编写实现代码之前。
它强制执行「红-绿-重构」循环,确保代码有测试覆盖且行为正确。
disable-model-invocation: true
---
# 测试驱动开发(TDD)
> **核心理念**:如果没有看到测试失败,你不知道它是否测试了正确的东西。
## 铁律
```
没有失败测试,就没有生产代码
```
在测试之前写了代码?**删除它**。重新开始。
**没有例外:**
- 不要保留作为「参考」
- 不要在写测试时「改编」它
- 不要看它
- 删除就是删除
从测试开始全新实现。句号。
**违反规则的字面,就是违反规则的精神。**
---
## 何时使用
**始终使用:**
- 新功能
- Bug 修复
- 重构
- 行为变更
**例外(需要用户确认):**
- 一次性原型
- 生成的代码
- 配置文件
想着「就这一次跳过 TDD」?停下来。那是合理化借口。
---
## 红-绿-重构循环
```
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 红 │ ──→ │ 绿 │ ──→ │ 重构 │
│ 写失败 │ │ 最小代码│ │ 清理 │
│ 测试 │ │ 通过 │ │ │
└─────────┘ └─────────┘ └─────────┘
↑ │
└───────────────────────────────┘
```
### 红 - 写失败测试
写一个最小的测试,展示应该发生什么。
**好的测试:**
```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);
});
```
清晰的名称,测试真实行为,只测一件事
**差的测试:**
```typescript
test('重试有效', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(2);
});
```
模糊的名称,测试 mock 而非代码
**要求:**
- 一个行为
- 清晰的名称
- 真实代码(除非无法避免才用 mock)
### 验证红 - 看它失败
**必须。绝不跳过。**
```bash
npm test path/to/test.test.ts
```
确认:
- 测试失败(不是报错)
- 失败消息符合预期
- 因为功能缺失而失败(不是拼写错误)
**测试通过了?** 你在测试已有行为。修复测试。
**测试报错了?** 修复错误,重新运行直到它正确失败。
### 绿 - 最小代码
写最简单的代码让测试通过。
**好的:**
```typescript
async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
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<T>(
fn: () => Promise<T>,
options?: {
maxRetries?: number;
backoff?: 'linear' | 'exponential';
onRetry?: (attempt: number) => void;
}
): Promise<T> {
// YAGNI - 你不需要它
}
```
过度工程
不要添加功能、重构其他代码或「改进」超出测试要求的内容。
### 验证绿 - 看它通过
**必须。**
```bash
npm test path/to/test.test.ts
```
确认:
- 测试通过
- 其他测试仍然通过
- 输出干净(无错误、警告)
**测试失败了?** 修复代码,不是测试。
**其他测试失败了?** 现在就修复。
### 重构 - 清理
只在绿色之后:
- 消除重复
- 改进命名
- 提取辅助函数
保持测试绿色。不要添加行为。
### 重复
下一个失败测试测下一个功能。
---
## 好测试的特征
| 质量 | 好 | 差 |
|------|----|----|
| **最小** | 一件事。名称里有「和」?拆分它。 | `test('验证邮箱和域名和空白')` |
| **清晰** | 名称描述行为 | `test('test1')` |
| **显示意图** | 展示期望的 API | 模糊代码应该做什么 |
---
## 为什么顺序重要
**「我先写代码再写测试验证」**
代码之后写的测试立即通过。立即通过什么都不证明:
- 可能测错东西
- 可能测实现而非行为
- 可能遗漏你忘记的边缘情况
- 你从没看到它捕获 bug
测试优先强制你看到测试失败,证明它确实在测试某些东西。
**「删除 X 小时的工作太浪费了」**
沉没成本谬误。时间已经过去了。你现在的选择:
- 删除并用 TDD 重写(再花 X 小时,高置信度)
- 保留并之后加测试(30 分钟,低置信度,可能有 bug)
「浪费」是保留你无法信任的代码。没有真正测试的工作代码是技术债务。
---
## 常见合理化借口
| 借口 | 现实 |
|------|------|
| 「太简单不需要测试」 | 简单代码也会坏。测试只需 30 秒。 |
| 「我之后写测试」 | 立即通过的测试什么都不证明。 |
| 「之后写测试达到同样目的」 | 之后测试 =「这做了什么?」优先测试 =「这应该做什么?」 |
| 「已经手动测试过了」 | 临时 ≠ 系统化。无记录,无法重跑。 |
| 「保留作为参考,先写测试」 | 你会改编它。那就是之后测试。删除就是删除。 |
| 「TDD 会拖慢我」 | TDD 比调试更快。务实 = 测试优先。 |
| 「手动测试更快」 | 手动不能证明边缘情况。每次改动都要重测。 |
---
## 当卡住时
| 问题 | 解决方案 |
|------|----------|
| 不知道怎么测试 | 写理想 API。先写断言。问协作者。 |
| 测试太复杂 | 设计太复杂。简化接口。 |
| 必须 mock 所有东西 | 代码耦合太紧。使用依赖注入。 |
| 测试 setup 太庞大 | 提取辅助函数。还是复杂?简化设计。 |
---
## 危险信号 - 停下来重新开始
- 测试之前写了代码
- 实现之后写测试
- 测试立即通过
- 无法解释为什么测试失败
- 测试「之后」添加
- 合理化「就这一次」
- 「我已经手动测试过了」
- 「之后测试达到同样目的」
- 「保留作为参考」或「改编现有代码」
- 「已经花了 X 小时,删除太浪费」
- 「TDD 是教条主义,我在务实」
- 「这个情况不一样...」
**以上所有都意味着:删除代码。用 TDD 重新开始。**
---
## 示例:Bug 修复
**Bug:** 空邮箱被接受
**红**
```typescript
test('拒绝空邮箱', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('邮箱必填');
});
```
**验证红**
```bash
$ npm test
FAIL: expected '邮箱必填', got undefined
```
**绿**
```typescript
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: '邮箱必填' };
}
// ...
}
```
**验证绿**
```bash
$ npm test
PASS
```
**重构**
如果需要,为多个字段提取验证。
---
## 验证清单
标记工作完成之前:
- [ ] 每个新函数/方法都有测试
- [ ] 实现之前看到每个测试失败
- [ ] 每个测试因预期原因失败(功能缺失,不是拼写错误)
- [ ] 写了最小代码让每个测试通过
- [ ] 所有测试通过
- [ ] 输出干净(无错误、警告)
- [ ] 测试使用真实代码(只在无法避免时用 mock)
- [ ] 边缘情况和错误已覆盖
不能勾选所有框?你跳过了 TDD。重新开始。
---
## 与调试集成
发现 bug?写一个复现它的失败测试。遵循 TDD 循环。测试证明修复并防止回归。
绝不在没有测试的情况下修复 bug。
---
## 辅助材料
- [测试反模式](./references/testing-anti-patterns.md) - 避免常见陷阱
---
## 最终规则
```
生产代码 → 测试存在且先失败
否则 → 不是 TDD
```
没有用户许可不能例外。
Files in this package (2)
- SKILL.md 8 KB
- references/testing-anti-patterns.md 6 KB
Versions
| Repository | Score | Licence |
|---|---|---|
| This page · jerrylalala/compound-engineering | 96 | MIT |
| GanyuanRan/Aegis | 100 | MIT |
| lucasouzadev/agent-kit-skill | 100 | MIT |
| zeative/satgas | 100 | AGPL-3.0 |
| nikospavlopoulos/devops-mutant-powers | 100 | MIT |
| JohnStrunk/CommonCast | 100 | LGPL |
| Sanit-mann/The-Insectoids | 100 | MIT |
| mastro993/fstack | 100 | MIT |
| jelvodev/yaap | 100 | MIT |
Database snapshot: 13.11.2026 · package version 57e386b35b8a (SHA-256 digest of the entire package)
Downloads in the last 30 days: 0 · ranking
Report a problem with this skill: harmful content, incorrect descriptions or an author’s removal request.
Similar skills
- 95community-test-driven-development
Use when implementing any feature or bugfix, before writing implementation code. Enforces RED-GREEN-REFACTOR cycle with test-first approach. - 100tdd
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. - 100tdd
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. - 100tdd
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. - 100tdd
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests. - 100tdd
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.