shared/tdd

Exact captured content · path skill/skills/shared/tdd/SKILL.md · sha256 00a65198330d5082… · revision 26ff1bec681d9c0c40f1495022abe1362a9366d0

Download the exact captured file

---

name: shared/tdd
description: 测试驱动开发(TDD)方法论,Red-Green-Refactor 循环,先写失败测试再写实现
version: 1.0.0
agent: shared
type: methodology
user-invocable: false
agent-invocable: true
dependencies: []
triggers:

- 实现新功能前
- 修复 bug 前
- 重构代码前
- 任何行为变更前
  metadata:
  internal: true

---

# 测试驱动开发(TDD)

## 核心铁律

```
没有失败的测试,就不写生产代码。
```

如果先写了实现代码,**必须删除**,从测试重新开始。不保留"参考",不"边写测试边改"。

## 适用场景

**必须使用 TDD**:

- 新功能实现
- Bug 修复
- 代码重构
- 任何行为变更

**例外(需用户明确同意)**:

- 一次性原型代码
- 纯配置文件
- 自动生成的代码

## Red-Green-Refactor 循环

### 🔴 RED — 写失败测试

为**一个行为**写最小测试:

- 一个测试只测一件事(名称里有 "and" 就拆开)
- 名称描述行为,而非实现(`rejects empty email` 而非 `test1`)
- 用真实代码,除非依赖外部服务才 mock
- 展示你期望的 API 长什么样

```typescript
// ✅ 好:测试真实行为
test('rejects empty email', () => {
  expect(validateEmail('')).toBe(false);
});

// ❌ 坏:测试 mock 而非代码
test('email works', () => {
  const mock = jest.fn();
  mock('');
  expect(mock).toHaveBeenCalled();
});
```

### 验证 RED — 必须看到它失败

**这一步不可跳过。** 运行测试,确认:

```bash
# 前端
npx jest path/to/test.test.ts
# 后端
npm test -- path/to/test.test.ts
```

确认三件事:

1. 测试**失败**(不是通过)
2. 失败原因是**功能缺失**(不是拼写错误、导入错误)
3. 失败信息符合预期

**如果测试直接通过** → 你在测已有行为,测试写错了,修正它。
**如果测试报错(error)** → 修复错误(如导入路径),重跑直到它正确失败。

### 🟢 GREEN — 写最小实现

写**刚好让测试通过**的最少代码:

- 不添加测试未要求的功能
- 不重构、不"顺便优化"
- 不提前抽象

```typescript
// ✅ 好:刚好通过
function validateEmail(email: string): boolean {
  return email.trim().length > 0;
}

// ❌ 坏:过度工程
function validateEmail(
  email: string,
  options?: { allowPlus?: boolean; domainWhitelist?: string[] },
): boolean {
  // YAGNI
}
```

### 验证 GREEN — 必须看到它通过

运行测试,确认:

1. 新测试通过
2. **所有已有测试仍然通过**
3. 输出干净(无报错、无意外警告)

**如果新测试失败** → 修代码,不修测试。
**如果其他测试失败** → 立即修复。

### 🔵 REFACTOR — 重构(仅在 GREEN 之后)

测试全绿后才允许重构:

- 消除重复
- 改善命名
- 提取辅助函数
- **不新增行为**

重构后必须重新跑全部测试,保持全绿。

### 循环

下一个行为 → 回到 RED。

## Bug 修复 TDD

修复 bug 时,先写一个**复现 bug 的失败测试**:

```
1. 写测试复现 bug → 确认失败(RED)
2. 修复代码 → 确认通过(GREEN)
3. 这个测试就是回归保护
```

**禁止**在没有复现测试的情况下修复 bug。

## 验证清单

标记任务完成前,逐项确认:

- [ ] 每个新增函数/方法都有对应测试
- [ ] 每个测试都先看到它失败(RED 已验证)
- [ ] 失败原因是功能缺失,而非拼写/导入错误
- [ ] 实现代码是让测试通过的最小集
- [ ] 全部测试通过
- [ ] 测试用真实代码(仅在不可避免时 mock)
- [ ] 边界条件和错误路径已覆盖

**任何一项打不了勾 → 你跳过了 TDD,回到 RED 重新开始。**

## 常见借口与回应

| 借口                     | 回应                                 |
| ------------------------ | ------------------------------------ |
| "太简单了不用测"         | 简单代码也会坏,测试只要 30 秒       |
| "我之后补测试"           | 事后写的测试一跑就过,证明不了任何事 |
| "我手动测过了"           | 手动测试无记录、不可复跑、容易漏     |
| "已经写了代码,删了浪费" | 沉没成本。保留未验证的代码才是技术债 |
| "TDD 太慢"               | TDD 比事后调试快,省的是未来的时间   |
| "这个不一样因为..."      | 没有例外。删代码,从测试开始。       |

## 卡住时

| 问题          | 解法                            |
| ------------- | ------------------------------- |
| 不知道怎么测  | 先写你期望的 API 调用,再写断言 |
| 测试太复杂    | 说明设计太复杂,简化接口        |
| 什么都要 mock | 代码耦合太高,用依赖注入解耦    |
| 测试设置庞大  | 提取辅助函数;仍复杂就简化设计  |