Files
VibeEngineering/.claude/skills/test-driven-development/SKILL.md
闫旭隆 c484cafb45 Initial commit: VibeEngineering V2
- 两阶段分离:设计阶段人工确认,执行阶段全自动化
- 子代理驱动:Implementer → Spec Reviewer → Quality Reviewer
- 原生 Task 系统:使用 Claude Code Task 替代自定义状态管理
- 跨 Compact 恢复:PreCompact + SessionStart Hook(内联命令实现)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 18:00:55 +08:00

256 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: test-driven-development
description: 在实现任何功能或修复 bug 时使用,在写实现代码之前
---
# 测试驱动开发 (TDD)
## 概述
先写测试。看它失败。写最小代码通过测试。
**核心原则:** 如果你没有看到测试失败,你就不知道它是否测试了正确的东西。
**违反规则的字面意思就是违反规则的精神。**
## 何时使用
**总是:**
- 新功能
- Bug 修复
- 重构
- 行为变更
**例外(需要用户确认):**
- 一次性原型
- 生成的代码
- 配置文件
想着"就这一次跳过 TDD"?停下来。那是在找借口。
## 铁律
```
没有失败的测试,就不能写生产代码
```
先写代码再写测试?删除它。重新开始。
**没有例外:**
- 不要把它作为"参考"保留
- 不要在写测试时"调整"它
- 不要看它
- 删除就是删除
从测试重新实现。就这样。
## RED-GREEN-REFACTOR
```
RED写失败测试
↓ 验证失败正确
GREEN最小代码
↓ 验证通过全绿
REFACTOR清理
↓ 保持绿色
→ 下一个 → RED
```
### RED - 写失败的测试
写一个最小的测试,展示应该发生什么。
**好的示例:**
```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('retry works', async () => {
const mock = jest.fn()
.mockRejectedValueOnce(new Error())
.mockResolvedValueOnce('success');
await retryOperation(mock);
expect(mock).toHaveBeenCalledTimes(2);
});
```
模糊的名称,测试 mock 而不是代码
**要求:**
- 一个行为
- 清晰的名称
- 真实代码(除非不可避免,否则不用 mock
### 验证 RED - 看它失败
**强制执行。永远不要跳过。**
```bash
npm test path/to/test.test.ts
```
确认:
- 测试失败(不是报错)
- 失败消息是预期的
- 因为功能缺失而失败(不是拼写错误)
**测试通过了?** 你在测试已有行为。修复测试。
**测试报错了?** 修复错误,重新运行直到它正确失败。
### GREEN - 最小代码
写最简单的代码通过测试。
**好的示例:**
```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 - 过度设计
}
```
不要添加功能、重构其他代码,或"改进"超出测试范围的内容。
### 验证 GREEN - 看它通过
**强制执行。**
```bash
npm test path/to/test.test.ts
```
确认:
- 测试通过
- 其他测试仍然通过
- 输出干净(没有错误、警告)
**测试失败?** 修复代码,不是测试。
**其他测试失败?** 现在修复。
### REFACTOR - 清理
只有在绿色之后:
- 移除重复
- 改进名称
- 提取辅助函数
保持测试绿色。不要添加行为。
### 重复
下一个失败测试,测试下一个功能。
## 好的测试
| 质量 | 好 | 坏 |
|------|----|----|
| **最小** | 一件事。名称中有"和"?拆分它。 | `test('验证邮箱和域名和空格')` |
| **清晰** | 名称描述行为 | `test('test1')` |
| **展示意图** | 展示期望的 API | 隐藏代码应该做什么 |
## 为什么顺序很重要
**"我之后写测试来验证它能工作"**
事后写的测试立即通过。立即通过什么都证明不了:
- 可能测试了错误的东西
- 可能测试的是实现,不是行为
- 可能遗漏了你忘记的边界情况
- 你从未看到它捕获 bug
先测试强迫你看到测试失败,证明它确实在测试什么。
## 常见借口
| 借口 | 现实 |
|------|------|
| "太简单不需要测试" | 简单代码也会出错。测试只需 30 秒。 |
| "我之后再测试" | 测试立即通过什么都证明不了。 |
| "已经手动测试过了" | 临时 ≠ 系统化。没有记录,无法重新运行。 |
| "删除 X 小时的工作太浪费" | 沉没成本谬误。保留不可信的代码才是浪费。 |
| "TDD 太教条了,务实意味着灵活" | TDD 就是务实的:更快发现 bug防止回归。 |
| "事后测试能达到相同目的" | 不。事后测试回答"这做了什么?"先测试回答"这应该做什么?" |
## 危险信号 - 停下来重新开始
- 先写代码再写测试
- 实现后才写测试
- 测试立即通过
- 无法解释测试为什么失败
- 测试是"之后"添加的
- 合理化"就这一次"
- "我已经手动测试过了"
- "保留作为参考"或"调整现有代码"
**所有这些意味着:删除代码。用 TDD 重新开始。**
## 验证清单
完成工作前:
- [ ] 每个新函数/方法都有测试
- [ ] 在实现之前看到每个测试失败
- [ ] 每个测试失败的原因是预期的(功能缺失,不是拼写错误)
- [ ] 为每个测试写了最小代码通过
- [ ] 所有测试通过
- [ ] 输出干净(没有错误、警告)
- [ ] 测试使用真实代码mock 只在不可避免时使用)
- [ ] 边界情况和错误都有覆盖
无法勾选所有项?你跳过了 TDD。重新开始。
## 卡住时
| 问题 | 解决方案 |
|------|----------|
| 不知道如何测试 | 先写期望的 API。先写断言。问用户。 |
| 测试太复杂 | 设计太复杂。简化接口。 |
| 必须 mock 所有东西 | 代码耦合太紧。使用依赖注入。 |
| 测试设置太大 | 提取辅助函数。仍然复杂?简化设计。 |
## 最终规则
```
生产代码 → 测试存在并且先失败了
否则 → 不是 TDD
```
没有用户许可不能有例外。