---
name: tdd
description: 使用红-绿-重构循环的测试驱动开发。当用户想要使用 TDD 构建功能或修复错误、提到"红-绿-重构"、想要集成测试或要求测试优先开发时使用。
---

# 测试驱动开发

## 理念

**核心原则**：测试应通过公共接口验证行为，而不是实现细节。代码可以完全改变；测试不应改变。

**好测试**是集成风格的：它们通过公共 API 执行真实的代码路径。它们描述系统**做**什么，而不是**如何**做。一个好测试读起来像规范——"用户可以用有效购物车结账"准确告诉你存在什么能力。这些测试在重构后依然存活，因为它们不关心内部结构。

**坏测试**与实现耦合。它们模拟内部协作者、测试私有方法、或通过外部手段验证（例如直接查询数据库而不是使用接口）。警告信号：当你重构时测试失败，但行为没有改变。如果你重命名了一个内部函数而测试失败，那些测试测试的是实现，而不是行为。

示例见 [tests.md](tests.md)，模拟指南见 [mocking.md](mocking.md)。

## 反模式：水平分层

**不要先写所有测试，再写所有实现。** 这是"水平分层"——把 RED 视为"写所有测试"，把 GREEN 视为"写所有代码"。

这会产生**垃圾测试**：

- 批量写入的测试测试的是**想象中的**行为，而不是**实际的**行为
- 你最终测试的是事物的**形态**（数据结构、函数签名），而不是面向用户的行为
- 测试对真实变化不敏感——行为破坏时它们通过，行为完好时它们失败
- 你超出了前大灯的范围，在理解实现之前就承诺了测试结构

**正确方法**：通过示踪子弹进行垂直切片。一个测试 → 一个实现 → 重复。每个测试响应你从前一个周期中学到的东西。因为你刚刚写了代码，你确切知道什么行为重要以及如何验证它。

```
错误（水平）：
  RED:   test1, test2, test3, test4, test5
  GREEN: impl1, impl2, impl3, impl4, impl5

正确（垂直）：
  RED→GREEN: test1→impl1
  RED→GREEN: test2→impl2
  RED→GREEN: test3→impl3
  ...
```

## 工作流程

### 1. 规划

探索代码库时，使用项目的领域词汇表，使测试名称和接口词汇与项目语言一致，并尊重你触及区域中的 ADR。

在写任何代码之前：

- [ ] 与用户确认需要哪些接口更改
- [ ] 与用户确认要测试哪些行为（排序优先级）
- [ ] 识别[深模块](deep-modules.md)的机会（小接口，深实现）
- [ ] 为[可测试性](interface-design.md)设计接口
- [ ] 列出要测试的行为（不是实现步骤）
- [ ] 获取用户对计划的批准

问："公共接口应该是什么样子？哪些行为最重要需要测试？"

**你无法测试所有东西。** 与用户确认确切哪些行为最重要。将测试工作集中在关键路径和复杂逻辑上，而不是每个可能的边缘情况。

### 2. 示踪子弹

写一个测试，确认系统中**一件事**：

```
RED:   为第一个行为写测试 → 测试失败
GREEN: 写最小代码通过 → 测试通过
```

这是你的示踪子弹——证明路径端到端可行。

### 3. 增量循环

对每个剩余的行为：

```
RED:   写下个测试 → 失败
GREEN: 最小代码通过 → 通过
```

规则：

- 一次一个测试
- 只够通过当前测试的代码
- 不要预期未来的测试
- 保持测试专注于可观察行为

### 4. 重构

所有测试通过后，寻找[重构候选](refactoring.md)：

- [ ] 提取重复
- [ ] 深化模块（将复杂性移到简单接口后面）
- [ ] 在自然的地方应用 SOLID 原则
- [ ] 考虑新代码揭示了关于现有代码的什么
- [ ] 每个重构步骤后运行测试

**永远不要在 RED 时重构。** 先到 GREEN。

## 每个周期的检查清单

```
[ ] 测试描述行为，而不是实现
[ ] 测试仅使用公共接口
[ ] 测试在内部重构后仍然存活
[ ] 代码对此测试而言是最小的
[ ] 没有添加推测性功能
```
