# Test Driven Development

> 实现任何功能或修复 bug 时，写实现代码之前必须使用。铁律：没有先看到失败的测试，就没有生产代码。触发词：写代码、实现功能、修bug、TDD、写测试。

- Skill: `yinqd3/test-driven-development` (Agent Skill)
- Install (CLI): `npx skillmds@latest add yinqd3/test-driven-development`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yinqd3/test-driven-development/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yinqd3 (https://skillmd.com/u/yinqd3)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yinqd3/test-driven-development

---


# 测试驱动开发（TDD）

## 核心原则

**先写测试，看它失败，写最少代码让它通过。**

如果你没看到测试失败，你就不知道它是否测试了正确的东西。

## 铁律

```
没有先看到失败的测试 = 没有生产代码
```

先写代码再写测试？删掉，重新来。

- 不要保留做"参考"
- 不要在写测试时"改编"它
- 不要看它
- 删除就是删除

## 何时使用

**永远：**
- 新功能
- Bug 修复
- 重构
- 行为变更

**例外（需要人类伙伴确认）：**
- 一次性原型
- 自动生成的代码
- 配置文件

"就这一次跳过 TDD 吧"？停。那是合理化借口。

## 红-绿-重构循环

### RED — 写失败测试

写一个最小测试展示应该发生什么。

**好的测试：**
```python
def test_retries_failed_operations_three_times():
    attempts = 0
    def operation():
        nonlocal attempts
        attempts += 1
        if attempts < 3:
            raise Exception("fail")
        return "success"
    result = retry(operation)
    assert result == "success"
    assert attempts == 3
```
清晰命名，测试真实行为，只测一件事

**差的测试：**
```python
def test_retry():
    mock = Mock(side_effect=[Exception, Exception, "success"])
    retry(mock)
    assert mock.call_count == 3
```
模糊命名，测 mock 不是测代码

**要求：**
- 一个行为一个测试
- 清晰的描述性名称
- 测试真实代码（非必要不用 mock）

### 验证 RED — 看它失败

**强制步骤，不可跳过。**

```
pytest tests/path/test.py::test_name -v
```

确认：
- 测试失败（不是报错）
- 失败信息符合预期
- 失败原因 = 功能缺失，不是拼写错误

**测试直接通过？** 你在测已有行为，修正测试。

### GREEN — 最少代码

写最简单的代码让测试通过。不要：
- 加测试不需要的选项
- 重构其他代码
- "改进"超过测试要求的范围

### 验证 GREEN — 看它通过

**强制步骤。**

```
pytest tests/path/test.py::test_name -v
```

确认：
- 目标测试通过
- 所有其他测试仍然通过
- 输出干净（无错误、无警告）

### REFACTOR — 清理

只在绿色之后：
- 消除重复
- 改进命名
- 提取辅助函数

保持测试绿色，不要添加行为。

### 重复

下一个失败测试 → 下一个功能。

## 好测试的标准

| 品质 | 好的 | 差的 |
|:---|:---|:---|
| 最小 | 一件事。名字里有"和"？拆开。 | `test_validates_email_and_domain()` |
| 清晰 | 名字描述行为 | `test1`、`test_works` |
| 展示意图 | 展示期望的 API | 隐藏代码应该做什么 |

## 为什么顺序重要

**"我先写代码再补测试来验证"**

事后写的测试立刻通过。立刻通过什么也证明不了：
- 可能测了错的东西
- 可能测了实现而非行为
- 可能漏了边界情况
- 你从来没见过它抓到 bug

## 常见借口粉碎

| 借口 | 现实 |
|:---|:---|
| "太简单不用测" | 简单代码也会坏。写测试只花 30 秒。 |
| "我稍后补测试" | 测试立即通过证明不了任何事。 |
| "我已经手动测过了" | 临时的 ≠ 系统性的。没有记录，无法重跑。 |
| "删掉 X 小时工作太浪费" | 沉没成本谬误。保留未验证代码 = 技术债。 |
| "TDD 太死板，实际点" | TDD 就是实际的：提交前找到 bug（比事后调试快）。 |
| "保留做参考，先写测试" | 你会改编它。那就是测试后补。删除就是删除。 |

## 红灯 — 停，重来

以下任何情况出现，意味着跳过 TDD：
- 测试之前写了代码
- 代码后补的测试
- 测试立即通过
- 解释不了测试为什么失败
- "我稍后加测试"
- 合理化"就这一次"
- "我已经手动测过了"

**以上全部意味着：删代码，用 TDD 重来。**

## 验证清单

完成前检查：
- [ ] 每个新函数/方法有测试
- [ ] 看到每个测试在实现前失败
- [ ] 每个测试因预期原因失败（功能缺失，不是拼写错误）
- [ ] 写了最少代码让每个测试通过
- [ ] 所有测试通过
- [ ] 输出干净
- [ ] 测试用真实代码（mock 只在不可避时）
- [ ] 覆盖边界情况和错误

