<!-- title: Claude Code TDD实战：测试驱动开发全流程 -->

# Claude Code TDD实战：测试驱动开发全流程

## 概述

本教程深入讲解如何在 Claude Code 中实践测试驱动开发（TDD）。你将学会 Red-Green-Refactor 循环的 AI 辅助实现、TDD Skill 的配置与使用、自定义 TDD 工作流的构建，以及通过 Hooks 实现自动化测试门禁。适合有一定 Claude Code 使用经验、希望提升代码质量的开发者。

**适合人群**：已掌握 Claude Code 基础操作，了解至少一种测试框架（pytest、Jest、Vitest 等）的开发者。

**学习目标**：
- 理解 Claude Code + TDD 的独特优势
- 掌握内置 TDD Skill 的配置与使用
- 构建自定义 TDD 工作流（Skill + Subagent）
- 通过 Hooks 实现测试门禁自动化
- 完成一个完整的 TDD 实战项目

## 前置条件

| 条件 | 说明 |
|------|------|
| Claude Code | 已安装并完成基础配置 |
| Python 3.10+ 或 Node.js 18+ | 本文以 Python + pytest 为主，辅以 Node.js + Vitest 示例 |
| 测试框架 | `pip install pytest` 或 `npm install -D vitest` |
| Git | 版本控制，方便回滚 |
| 基础知识 | 了解 Python/JS 语法，知道什么是单元测试 |

## 第一章：为什么 TDD + Claude Code 是绝配

### 传统 TDD 的痛点

测试驱动开发的核心循环是 **Red → Green → Refactor**：

1. **Red**：先写一个会失败的测试
2. **Green**：写最少量的代码让测试通过
3. **Refactor**：在不改变行为的前提下优化代码

传统 TDD 最大的痛点在于——**写测试比写实现更难**。开发者需要先思考接口设计，再描述期望行为，这对心智负担要求很高。结果是，大多数人在压力下跳过测试，直接写实现。

### Claude Code 如何改变游戏规则

Claude Code 的结构化能力使 TDD 的三个阶段都变得轻松：

| 阶段 | 传统方式 | Claude Code 方式 |
|------|---------|-----------------|
| Red | 手动设计测试用例，需要预见边界条件 | 描述需求，Claude 生成全面的测试套件 |
| Green | 手写最小实现，容易过度设计 | Claude 严格只写让测试通过的代码 |
| Refactor | 手动重构，容易引入回归 | Claude 重构后自动运行测试验证 |
| 循环 | 每轮需要手动切换思维模式 | 一条指令完成多轮 Red-Green-Refactor |

关键差异：**Claude Code 默认倾向"实现优先"**，你需要主动约束它走 TDD 流程。这正是本教程要解决的核心问题。

### 整体工作流架构

```mermaid
flowchart TB
    subgraph "TDD 循环"
        A["🔴 RED<br/>写失败测试"] --> B["🟢 GREEN<br/>最小实现"]
        B --> C["🔵 REFACTOR<br/>优化代码"]
        C --> D{测试通过?}
        D -->|是| E["需求完成?"]
        D -->|否| B
        E -->|否| A
        E -->|是| F["✅ 完成"]
    end

    subgraph "Claude Code 保障层"
        G["CLAUDE.md 规则<br/>强制TDD纪律"]
        H["TDD Skill<br/>结构化流程"]
        I["Hooks 门禁<br/>自动化验证"]
        J["Subagent<br/>并行测试"]
    end

    G --> A
    H --> A
    I --> D
    J --> D
```

## 第二章：环境搭建与基础配置

### 2.1 创建 TDD 项目

```bash
# 创建项目目录
mkdir claude-tdd-demo && cd claude-tdd-demo

# 初始化 Python 项目
python -m venv .venv
source .venv/bin/activate
pip install pytest pytest-cov

# 创建基础目录结构
mkdir -p src tests
touch src/__init__.py tests/__init__.py
```

预期目录结构：

```
claude-tdd-demo/
├── .venv/
├── src/
│   └── __init__.py
├── tests/
│   └── __init__.py
├── pyproject.toml
└── CLAUDE.md
```

### 2.2 配置 CLAUDE.md — TDD 纪律规则

在项目根目录创建 `CLAUDE.md`，写入 TDD 约束规则：

```markdown
# 项目规范

## TDD 规则（强制）

1. **永远先写测试，再写实现**。如果用户要求实现功能，先写测试。
2. 测试必须先失败（Red），才能写实现代码（Green）。
3. 实现代码只写让当前测试通过的最少量代码，不要过度设计。
4. 每次通过测试后，检查是否需要重构（Refactor）。
5. 运行测试的命令：`python -m pytest tests/ -v`
6. 覆盖率命令：`python -m pytest tests/ --cov=src --cov-report=term-missing`

## 代码规范

- 使用 Python type hints
- 函数必须有 docstring
- 测试文件命名：test_<module>.py
- 测试函数命名：test_<scenario>_<expected>
```

这段 CLAUDE.md 是你的 **TDD 宪法**。Claude Code 每次启动都会读取它，从源头上约束 AI 行为。

### 2.3 pytest 配置

创建 `pyproject.toml`：

```toml
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-v --tb=short"

[tool.coverage.run]
source = ["src"]
omit = ["tests/*"]
```

**常见问题**：

| 问题 | 原因 | 解决方案 |
|------|------|---------|
| `ModuleNotFoundError: No module named 'src'` | Python 路径未配置 | 在测试文件顶部添加 `import sys; sys.path.insert(0, '.')` 或用 `pip install -e .` |
| pytest 找不到测试文件 | 文件命名不规范 | 确保文件名以 `test_` 开头 |
| Claude Code 跳过测试直接写实现 | CLAUDE.md 约束不足 | 加强提示词，使用 TDD Skill |

## 第三章：手动 TDD 对话 — 掌握基本功

在引入 Skill 自动化之前，先通过手动对话理解 Claude Code 的 TDD 节奏。

### 3.1 第一轮：Red（写失败测试）

在 Claude Code 中输入：

```
我要实现一个密码验证器 PasswordValidator，要求：
1. 至少8个字符
2. 包含至少一个大写字母
3. 包含至少一个数字
4. 包含至少一个特殊字符

请先写测试，不要写实现。
```

Claude Code 生成测试文件 `tests/test_password_validator.py`：

```python
import pytest
from src.password_validator import PasswordValidator


class TestPasswordValidator:
    """密码验证器测试套件"""
    
    def test_valid_password_returns_true(self):
        """合法密码应返回 True"""
        validator = PasswordValidator()
        assert validator.validate("Str0ng!Pass") is True
    
    def test_too_short_returns_false(self):
        """少于8个字符应返回 False"""
        validator = PasswordValidator()
        assert validator.validate("Ab1!") is False
    
    def test_no_uppercase_returns_false(self):
        """没有大写字母应返回 False"""
        validator = PasswordValidator()
        assert validator.validate("lowercase1!") is False
    
    def test_no_digit_returns_false(self):
        """没有数字应返回 False"""
        validator = PasswordValidator()
        assert validator.validate("NoDigits!!") is False
    
    def test_no_special_char_returns_false(self):
        """没有特殊字符应返回 False"""
        validator = PasswordValidator()
        assert validator.validate("NoSpecial1") is False
    
    def test_empty_password_returns_false(self):
        """空密码应返回 False"""
        validator = PasswordValidator()
        assert validator.validate("") is False
    
    def test_exactly_8_chars_with_all_rules(self):
        """恰好8个字符且满足所有规则"""
        validator = PasswordValidator()
        assert validator.validate("Abcdef1!") is True
```

运行测试，确认全部失败：

```bash
$ python -m pytest tests/ -v
# 预期：7 FAILED（因为 PasswordValidator 类还不存在）
```

### 3.2 第二轮：Green（最小实现）

```
现在写最少的实现代码，让测试通过。不要添加任何额外功能。
```

Claude Code 生成 `src/password_validator.py`：

```python
import re


class PasswordValidator:
    """密码验证器，检查密码强度"""
    
    def validate(self, password: str) -> bool:
        """验证密码是否符合强度要求
        
        Args:
            password: 待验证的密码字符串
            
        Returns:
            True 如果密码满足所有规则，否则 False
        """
        if not password or len(password) < 8:
            return False
        if not re.search(r'[A-Z]', password):
            return False
        if not re.search(r'\d', password):
            return False
        if not re.search(r'[!@#$%^&*()_+\-=\[\]{}|;:\'",.<>?/\\`~]', password):
            return False
        return True
```

运行测试，确认全部通过：

```bash
$ python -m pytest tests/ -v
# 预期：7 passed
```

### 3.3 第三轮：Refactor（优化）

```
测试全部通过了。请重构代码，提升可读性和可维护性，
但不要改变任何外部行为。重构后再运行测试确认。
```

重构后的代码：

```python
import re
from dataclasses import dataclass


@dataclass
class PasswordRule:
    """密码规则定义"""
    pattern: str
    message: str


class PasswordValidator:
    """密码验证器，通过规则引擎检查密码强度"""
    
    RULES = [
        PasswordRule(r'.{8,}', "密码至少需要8个字符"),
        PasswordRule(r'[A-Z]', "密码需要包含至少一个大写字母"),
        PasswordRule(r'\d', "密码需要包含至少一个数字"),
        PasswordRule(r'[!@#$%^&*()_+\-=\[\]{}|;:\'",.<>?/\\`~]', "密码需要包含至少一个特殊字符"),
    ]
    
    def validate(self, password: str) -> bool:
        """验证密码是否符合所有强度规则"""
        return all(
            re.search(rule.pattern, password)
            for rule in self.RULES
        )
```

```bash
$ python -m pytest tests/ -v
# 预期：7 passed（行为不变）
```

这就是一轮完整的 **Red → Green → Refactor** 循环。

## 第四章：TDD Skill — 自动化 Red-Green-Refactor

手动对话虽然有效，但需要你在每一步给出明确指令。TDD Skill 将这个流程结构化，让 Claude Code 自动循环。

### 4.1 安装 Superpowers 插件（含 TDD Skill）

Superpowers 是 Claude Code 的官方推荐插件，内置 TDD Skill：

```bash
# 通过 Claude Code 的 /install-github-plugin 命令安装
claude /install-github-plugin anthropics/superpowers
```

安装后，TDD Skill 位于 `~/.claude/skills/superpowers/test-driven-development/`。

### 4.2 使用 TDD Skill 的三种方式

**方式一：通过 Skill 指令调用**

在 Claude Code 对话中输入：

```
/skill:test-driven-development

请实现一个 Shopping Cart 类：
- 可以添加商品（名称、价格、数量）
- 可以计算总价
- 可以移除商品
- 可以应用折扣码（百分比折扣）
```

TDD Skill 会自动启动 Red-Green-Refactor 循环：

```mermaid
sequenceDiagram
    participant U as 用户
    participant CC as Claude Code
    participant SK as TDD Skill
    participant T as pytest

    U->>CC: /skill:test-driven-development + 需求描述
    CC->>SK: 加载 Skill 指令
    SK->>CC: 约束：先写测试
    
    rect rgb(255, 200, 200)
        Note over CC,T: 🔴 RED 阶段
        CC->>CC: 生成测试文件
        CC->>T: 运行测试
        T-->>CC: FAILED ✗
    end
    
    rect rgb(200, 255, 200)
        Note over CC,T: 🟢 GREEN 阶段
        CC->>CC: 写最小实现
        CC->>T: 运行测试
        T-->>CC: PASSED ✓
    end
    
    rect rgb(200, 200, 255)
        Note over CC,T: 🔵 REFACTOR 阶段
        CC->>CC: 重构优化
        CC->>T: 运行测试
        T-->>CC: PASSED ✓
    end
    
    CC-->>U: 完成所有功能
```

**方式二：在 CLAUDE.md 中声明**

在 `CLAUDE.md` 中添加：

```markdown
## 开发流程

所有功能开发必须使用 TDD Skill。当收到功能需求时：
1. 调用 /skill:test-driven-development
2. 按照 Skill 指示的流程执行
3. 每完成一个功能点，运行测试确认
```

**方式三：通过自定义 Skill 实现更强的约束**

创建自定义 Skill 文件 `.claude/skills/tdd-strict.md`：

```markdown
---
name: tdd-strict
description: 严格 TDD 工作流，强制 Red-Green-Refactor 纪律
---

# 严格 TDD 工作流

## 执行规则

### 第一步：RED（必须失败）
1. 分析需求，识别所有测试场景
2. 生成测试文件，覆盖：正常路径、边界条件、错误路径
3. 运行测试，确认测试**失败**
4. 如果测试意外通过，说明测试写错了，必须修改

### 第二步：GREEN（最小实现）
1. 只写让当前失败测试通过的最少代码
2. 不添加任何测试未要求的功能
3. 不做任何优化或重构
4. 运行测试，确认**全部通过**

### 第三步：REFACTOR（安全优化）
1. 检查实现代码是否有重复、过长、命名不当等问题
2. 如果需要重构，先确认测试全部通过
3. 执行重构
4. 再次运行测试，确认**仍然全部通过**
5. 如果重构导致测试失败，立即回滚

### 循环
重复以上三步，直到所有需求都被满足。

### 测试命令
- 运行测试：`python -m pytest tests/ -v`
- 覆盖率：`python -m pytest tests/ --cov=src --cov-report=term-missing`
- 目标覆盖率：≥ 90%
```

### 4.3 Skill 执行过程详解

当你调用 TDD Skill 时，Claude Code 的内部行为如下：

```
用户输入: "实现一个 TodoList 类，支持增删改查"

TDD Skill 接管 →
  ├── 分析需求 → 识别 4 个功能点
  ├── Red #1: 写 add_todo 测试 → 运行 → FAILED ✓
  ├── Green #1: 实现 add_todo → 运行 → PASSED ✓
  ├── Refactor #1: 检查 → 无需重构
  ├── Red #2: 写 remove_todo 测试 → 运行 → FAILED ✓
  ├── Green #2: 实现 remove_todo → 运行 → PASSED ✓
  ├── Refactor #2: 检查 → 无需重构
  ├── Red #3: 写 update_todo 测试 → 运行 → FAILED ✓
  ├── Green #3: 实现 update_todo → 运行 → PASSED ✓
  ├── Refactor #3: 提取公共方法 → 运行 → PASSED ✓
  ├── Red #4: 写 list_todos 测试 → 运行 → FAILED ✓
  ├── Green #4: 实现 list_todos → 运行 → PASSED ✓
  ├── Refactor #4: 优化查询性能 → 运行 → PASSED ✓
  └── 最终覆盖率检查 → 95% ✓
```

## 第五章：自定义 TDD Skill + Subagent 编排

对于大型项目，可以构建更强大的 TDD 工作流——用 Skill 编排 Subagent 实现并行测试。

### 5.1 创建高级 TDD Skill

创建 `.claude/skills/tdd-parallel.md`：

```markdown
---
name: tdd-parallel
description: 并行 TDD 工作流，使用 Subagent 加速测试与实现
---

# 并行 TDD 工作流

## 工作流程

### 阶段 1：需求分析
1. 将用户需求拆分为独立的「功能单元」
2. 识别功能单元之间的依赖关系
3. 输出功能单元列表及依赖图

### 阶段 2：并行 RED
对于无依赖的功能单元，使用 Task 工具并行启动 Subagent：
- 每个 Subagent 负责为一个功能单元生成测试
- Subagent 完成后，汇总所有测试文件
- 运行所有测试，确认失败

### 阶段 3：顺序 GREEN
按照依赖顺序实现功能：
- 无依赖的功能优先实现
- 依赖其他功能的后续实现
- 每实现一个功能，运行相关测试

### 阶段 4：并行验证
使用 Task 工具并行运行：
- Subagent A: 运行全部单元测试
- Subagent B: 检查代码风格和类型
- Subagent C: 计算覆盖率

### 阶段 5：REFACTOR
基于验证结果进行重构，然后再次并行验证。

## 输出格式

每完成一个功能单元，输出：
- 功能名称
- 测试用例数
- 测试状态（RED/GREEN）
- 覆盖率
- 重构记录
```

### 5.2 使用 Subagent 实现 TDD 的关键代码模式

在实际操作中，你可以用这样的提示词驱动 Subagent TDD：

```
请使用 tdd-parallel skill 实现以下系统：

## 电商订单系统

### 功能单元
1. Order（订单）- 创建、取消、完成
2. Payment（支付）- 处理支付、退款
3. Inventory（库存）- 扣减、补充、查询
4. Notification（通知）- 发送邮件、短信

### 依赖关系
- Payment 依赖 Order
- Inventory 依赖 Order
- Notification 依赖 Order 和 Payment
```

Claude Code 会自动将功能拆分为可并行的分组：

```mermaid
graph LR
    subgraph "并行组 1: RED"
        A1["Order 测试"]
        A2["Payment 测试"]
        A3["Inventory 测试"]
        A4["Notification 测试"]
    end

    subgraph "顺序组: GREEN"
        B1["Order 实现"] --> B2["Payment 实现"]
        B1 --> B3["Inventory 实现"]
        B2 --> B4["Notification 实现"]
        B3 --> B4
    end

    subgraph "并行组 2: 验证"
        C1["单元测试"]
        C2["类型检查"]
        C3["覆盖率"]
    end

    A4 --> B1
    B4 --> C1
```

## 第六章：Hooks 门禁 — 自动化测试屏障

Hooks 是 Claude Code 的事件钩子系统，可以在关键时刻自动运行测试，防止 TDD 纪律被打破。

### 6.1 配置 Pre-Edit Hook

创建 `.claude/settings.json`：

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python -m pytest tests/ -q --tb=no 2>/dev/null && echo 'PASS' || echo 'FAIL: 测试未全部通过，请先修复'"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "python -m pytest tests/ -q --tb=short 2>&1 | tail -5"
          }
        ]
      }
    ]
  }
}
```

这个配置做了两件事：
1. **PreToolUse**：在 Claude Code 修改任何文件前，先检查现有测试是否通过。如果测试失败，给出警告。
2. **PostToolUse**：修改文件后，自动运行测试并显示结果。

### 6.2 更严格的 Hook — 区分测试文件和实现文件

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "FILE=\"$CLAUDE_FILE_PATH\"; if echo \"$FILE\" | grep -qv 'test_'; then echo '⚠️  你正在编辑实现文件。确认测试已写好？'; python -m pytest tests/ -q --tb=no 2>/dev/null; fi"
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "echo '🔔 TDD 提醒：先写测试，再写实现'"
          }
        ]
      }
    ]
  }
}
```

### 6.3 Hook 工作流全貌

```mermaid
flowchart LR
    A["Claude Code<br/>准备编辑文件"] --> B{PreToolUse Hook}
    B -->|"编辑测试文件"| C["✅ 允许"]
    B -->|"编辑实现文件"| D{测试存在?}
    D -->|"是"| C
    D -->|"否"| E["⚠️ 警告：<br/>请先写测试"]
    E --> F["Claude Code<br/>调整行为"]
    C --> G["编辑完成"]
    G --> H["PostToolUse Hook<br/>自动运行测试"]
    H --> I["显示测试结果"]
```

## 第七章：完整实战案例 — TDD 构建 REST API 验证层

本章用一个完整的实战案例，展示 TDD 全流程。我们将构建一个 FastAPI 应用的请求数据验证层。

### 7.1 项目初始化

```bash
mkdir api-validator && cd api-validator
python -m venv .venv && source .venv/bin/activate
pip install pytest pytest-cov fastapi pydantic httpx
mkdir -p src tests
```

### 7.2 CLAUDE.md 配置

```markdown
# 项目规范

## TDD 规则
1. 先写测试，确认失败，再写实现
2. 测试命令：`python -m pytest tests/ -v`
3. 覆盖率目标：≥ 90%
4. 覆盖率命令：`python -m pytest tests/ --cov=src --cov-report=term-missing`

## 技术栈
- Python 3.12
- FastAPI + Pydantic v2
- pytest + httpx（用于 API 测试）

## 代码风格
- 使用 type hints
- 函数 docstring 使用 Google 风格
```

### 7.3 Round 1：用户注册验证

**输入 Claude Code：**

```
使用 TDD 流程实现用户注册数据验证器 UserRegistrationValidator：

验证规则：
- username: 3-20字符，只允许字母数字下划线
- email: 合法邮箱格式
- password: 至少8字符，包含大小写字母和数字
- age: 可选，如果提供必须 >= 18

请先写测试。
```

**Claude Code 生成测试 `tests/test_user_registration.py`：**

```python
import pytest
from src.validators import UserRegistrationValidator, ValidationResult


class TestUsernameValidation:
    """用户名验证测试"""
    
    def test_valid_username(self):
        result = UserRegistrationValidator.validate_username("john_doe123")
        assert result.is_valid is True
    
    def test_too_short_username(self):
        result = UserRegistrationValidator.validate_username("ab")
        assert result.is_valid is False
        assert "3" in result.errors[0]
    
    def test_too_long_username(self):
        result = UserRegistrationValidator.validate_username("a" * 21)
        assert result.is_valid is False
    
    def test_special_chars_in_username(self):
        result = UserRegistrationValidator.validate_username("john@doe")
        assert result.is_valid is False


class TestEmailValidation:
    """邮箱验证测试"""
    
    def test_valid_email(self):
        result = UserRegistrationValidator.validate_email("user@example.com")
        assert result.is_valid is True
    
    def test_missing_at_sign(self):
        result = UserRegistrationValidator.validate_email("userexample.com")
        assert result.is_valid is False
    
    def test_empty_email(self):
        result = UserRegistrationValidator.validate_email("")
        assert result.is_valid is False


class TestPasswordValidation:
    """密码验证测试"""
    
    def test_valid_password(self):
        result = UserRegistrationValidator.validate_password("SecurePass123")
        assert result.is_valid is True
    
    def test_no_uppercase(self):
        result = UserRegistrationValidator.validate_password("lowercase123")
        assert result.is_valid is False
    
    def test_no_digit(self):
        result = UserRegistrationValidator.validate_password("NoDigitsHere")
        assert result.is_valid is False


class TestAgeValidation:
    """年龄验证测试"""
    
    def test_valid_age(self):
        result = UserRegistrationValidator.validate_age(25)
        assert result.is_valid is True
    
    def test_underage(self):
        result = UserRegistrationValidator.validate_age(16)
        assert result.is_valid is False
    
    def test_optional_age_none(self):
        result = UserRegistrationValidator.validate_age(None)
        assert result.is_valid is True


class TestFullRegistration:
    """完整注册验证测试"""
    
    def test_valid_registration(self):
        data = {
            "username": "john_doe",
            "email": "john@example.com",
            "password": "SecurePass123",
            "age": 25
        }
        result = UserRegistrationValidator.validate(data)
        assert result.is_valid is True
    
    def test_multiple_errors(self):
        data = {
            "username": "a",
            "email": "invalid",
            "password": "short"
        }
        result = UserRegistrationValidator.validate(data)
        assert result.is_valid is False
        assert len(result.errors) >= 3
```

运行测试 — 全部失败：

```bash
$ python -m pytest tests/ -v
# FAILED errors (18 failures) — 模块不存在
```

**输入 Claude Code：**

```
现在写最小实现，让所有测试通过。
```

**Claude Code 生成 `src/validators.py`：**

```python
import re
from dataclasses import dataclass, field


@dataclass
class ValidationResult:
    """验证结果"""
    is_valid: bool
    errors: list[str] = field(default_factory=list)


class UserRegistrationValidator:
    """用户注册数据验证器"""
    
    @staticmethod
    def validate_username(username: str) -> ValidationResult:
        """验证用户名：3-20字符，只允许字母数字下划线"""
        errors = []
        if len(username) < 3:
            errors.append("用户名至少需要3个字符")
        if len(username) > 20:
            errors.append("用户名不能超过20个字符")
        if not re.match(r'^[a-zA-Z0-9_]+$', username):
            errors.append("用户名只能包含字母、数字和下划线")
        return ValidationResult(is_valid=len(errors) == 0, errors=errors)
    
    @staticmethod
    def validate_email(email: str) -> ValidationResult:
        """验证邮箱格式"""
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        is_valid = bool(re.match(pattern, email)) if email else False
        errors = [] if is_valid else ["邮箱格式不正确"]
        return ValidationResult(is_valid=is_valid, errors=errors)
    
    @staticmethod
    def validate_password(password: str) -> ValidationResult:
        """验证密码：至少8字符，包含大小写字母和数字"""
        errors = []
        if len(password) < 8:
            errors.append("密码至少需要8个字符")
        if not re.search(r'[A-Z]', password):
            errors.append("密码需要包含至少一个大写字母")
        if not re.search(r'[a-z]', password):
            errors.append("密码需要包含至少一个小写字母")
        if not re.search(r'\d', password):
            errors.append("密码需要包含至少一个数字")
        return ValidationResult(is_valid=len(errors) == 0, errors=errors)
    
    @staticmethod
    def validate_age(age: int | None) -> ValidationResult:
        """验证年龄：可选，如果提供必须 >= 18"""
        if age is None:
            return ValidationResult(is_valid=True)
        if age < 18:
            return ValidationResult(is_valid=False, errors=["年龄必须大于等于18岁"])
        return ValidationResult(is_valid=True)
    
    @classmethod
    def validate(cls, data: dict) -> ValidationResult:
        """验证完整注册数据"""
        all_errors = []
        
        username_result = cls.validate_username(data.get("username", ""))
        if not username_result.is_valid:
            all_errors.extend(username_result.errors)
        
        email_result = cls.validate_email(data.get("email", ""))
        if not email_result.is_valid:
            all_errors.extend(email_result.errors)
        
        password_result = cls.validate_password(data.get("password", ""))
        if not password_result.is_valid:
            all_errors.extend(password_result.errors)
        
        if "age" in data:
            age_result = cls.validate_age(data["age"])
            if not age_result.is_valid:
                all_errors.extend(age_result.errors)
        
        return ValidationResult(is_valid=len(all_errors) == 0, errors=all_errors)
```

运行测试 — 全部通过：

```bash
$ python -m pytest tests/ -v --cov=src --cov-report=term-missing
# 18 passed, 覆盖率 98%
```

### 7.4 Round 2：集成 FastAPI（续 TDD）

继续输入：

```
现在用 TDD 方式将 UserRegistrationValidator 集成到 FastAPI 端点。

POST /api/register 接收 JSON 数据，返回验证结果：
- 200: 验证通过
- 422: 验证失败，返回错误列表

请先写 API 测试。
```

**测试文件 `tests/test_api.py`：**

```python
import pytest
from fastapi.testclient import TestClient
from src.app import app


@pytest.fixture
def client():
    return TestClient(app)


class TestRegisterEndpoint:
    """注册端点测试"""
    
    def test_valid_registration_returns_200(self, client):
        response = client.post("/api/register", json={
            "username": "john_doe",
            "email": "john@example.com",
            "password": "SecurePass123",
            "age": 25
        })
        assert response.status_code == 200
        data = response.json()
        assert data["is_valid"] is True
    
    def test_invalid_registration_returns_422(self, client):
        response = client.post("/api/register", json={
            "username": "a",
            "email": "invalid",
            "password": "short"
        })
        assert response.status_code == 422
        data = response.json()
        assert data["is_valid"] is False
        assert len(data["errors"]) > 0
    
    def test_missing_body_returns_422(self, client):
        response = client.post("/api/register", json={})
        assert response.status_code == 422
```

**实现文件 `src/app.py`：**

```python
from fastapi import FastAPI
from pydantic import BaseModel
from src.validators import UserRegistrationValidator


app = FastAPI(title="用户注册 API")


class RegistrationRequest(BaseModel):
    username: str
    email: str
    password: str
    age: int | None = None


@app.post("/api/register")
async def register(request: RegistrationRequest):
    """处理用户注册请求"""
    result = UserRegistrationValidator.validate(request.model_dump())
    if result.is_valid:
        return {"is_valid": True, "message": "注册数据验证通过"}
    from fastapi.responses import JSONResponse
    return JSONResponse(
        status_code=422,
        content={"is_valid": False, "errors": result.errors}
    )
```

```bash
$ python -m pytest tests/ -v --cov=src
# 21 passed
```

### 7.5 最终项目结构

```
api-validator/
├── CLAUDE.md              # TDD 规则
├── .claude/
│   ├── settings.json      # Hooks 配置
│   └── skills/
│       └── tdd-strict.md  # 自定义 TDD Skill
├── pyproject.toml
├── src/
│   ├── __init__.py
│   ├── app.py             # FastAPI 应用
│   └── validators.py      # 验证器实现
└── tests/
    ├── __init__.py
    ├── test_api.py         # API 端点测试
    └── test_user_registration.py  # 验证器单元测试
```

## 第八章：Node.js/TypeScript 版 TDD 实战

对于前端和 Node.js 项目，TDD 流程同样适用。以下是 Vitest + TypeScript 的快速指南。

### 8.1 项目初始化

```bash
mkdir tdd-node-demo && cd tdd-node-demo
npm init -y
npm install -D vitest typescript @types/node
npx tsc --init
```

`CLAUDE.md` 配置：

```markdown
## TDD 规则
1. 先写测试，再写实现
2. 测试命令：`npx vitest run`
3. 监听模式：`npx vitest`
4. 覆盖率：`npx vitest run --coverage`
```

### 8.2 示例：TDD 构建 URL 解析器

**测试 `tests/url-parser.test.ts`：**

```typescript
import { describe, it, expect } from 'vitest'
import { UrlParser } from '../src/url-parser'

describe('UrlParser', () => {
  it('parses valid URL', () => {
    const result = UrlParser.parse('https://example.com/path?q=test')
    expect(result.protocol).toBe('https')
    expect(result.domain).toBe('example.com')
    expect(result.path).toBe('/path')
    expect(result.queryParams.q).toBe('test')
  })

  it('throws on invalid URL', () => {
    expect(() => UrlParser.parse('not-a-url')).toThrow()
  })

  it('handles URL without path', () => {
    const result = UrlParser.parse('https://example.com')
    expect(result.path).toBe('/')
  })

  it('handles multiple query params', () => {
    const result = UrlParser.parse('https://example.com?a=1&b=2')
    expect(result.queryParams).toEqual({ a: '1', b: '2' })
  })
})
```

**实现 `src/url-parser.ts`：**

```typescript
export interface ParsedUrl {
  protocol: string
  domain: string
  path: string
  queryParams: Record<string, string>
}

export class UrlParser {
  static parse(url: string): ParsedUrl {
    try {
      const parsed = new URL(url)
      const queryParams: Record<string, string> = {}
      parsed.searchParams.forEach((value, key) => {
        queryParams[key] = value
      })
      return {
        protocol: parsed.protocol.replace(':', ''),
        domain: parsed.hostname,
        path: parsed.pathname || '/',
        queryParams,
      }
    } catch {
      throw new Error(`Invalid URL: ${url}`)
    }
  }
}
```

```bash
$ npx vitest run
# ✓ UrlParser (4 tests) 4 passed
```

## 第九章：高级技巧与最佳实践

### 9.1 上下文管理 — TDD 的隐藏关键

TDD 会产生大量文件（测试 + 实现），上下文窗口容易溢出。解决方案：

**策略 1：分层测试，逐步聚焦**

```
# 不要一次性把所有测试都塞进上下文
# 先写核心功能的测试，实现通过后，再添加边界测试

第一步：请为 User 类的核心功能（创建、序列化）写测试
# ... 实现通过 ...

第二步：现在添加边界条件测试（空值、超长字符串、特殊字符）
```

**策略 2：使用 /compact 压缩上下文**

每完成一轮 Red-Green-Refactor 后，运行 `/compact` 压缩对话历史，释放上下文空间。

**策略 3：用 CLAUDE.md 管理测试约定**

```markdown
## 测试约定
- 每个模块一个测试文件
- 测试类按功能分组，不要一个类超过10个测试方法
- 使用 pytest.mark.parametrize 批量测试相似场景
- fixture 放在 conftest.py 中共享
```

### 9.2 避免 Claude Code 的 TDD 反模式

| 反模式 | 描述 | 解决方案 |
|--------|------|---------|
| **测试后置** | Claude Code 先写实现再补测试 | 在 CLAUDE.md 中强调"永远先写测试" |
| **过度测试** | 为每个 getter/setter 写测试 | 只测业务逻辑，不测框架行为 |
| **脆弱测试** | 测试依赖实现细节 | 测试公共接口，不测私有方法 |
| **批量跳步** | 一次性生成所有测试和实现 | 用 Skill 强制逐步执行 |
| **忽略 Red** | 跳过"确认测试失败"的步骤 | 在 Skill 中要求先运行测试确认失败 |

### 9.3 多文件 TDD 的策略

对于涉及多个文件的功能（如前后端联调），使用分批策略：

```mermaid
flowchart TD
    A["需求分析"] --> B["识别模块边界"]
    B --> C["模块A: TDD循环"]
    B --> D["模块B: TDD循环"]
    B --> E["模块C: TDD循环"]
    C --> F["集成测试"]
    D --> F
    E --> F
    F --> G{集成测试通过?}
    G -->|是| H["完成"]
    G -->|否| I["定位失败模块"]
    I --> C
```

在 Claude Code 中的实际操作：

```
请分三个阶段用 TDD 方式实现以下系统：

阶段1: 先用 TDD 实现 UserRepository（数据访问层）
阶段2: 等阶段1完成后，再用 TDD 实现 UserService（业务逻辑层）
阶段3: 等阶段2完成后，再用 TDD 实现 UserAPI（API 层）

每个阶段都要独立完成 Red-Green-Refactor 循环。
```

### 9.4 覆盖率驱动的 TDD

在 CLAUDE.md 中设置覆盖率目标：

```markdown
## 覆盖率要求
- 行覆盖率：≥ 90%
- 分支覆盖率：≥ 80%
- 每次实现新功能后，运行覆盖率检查
- 如果覆盖率不足，优先补充边界条件测试
```

然后用这条提示词让 Claude Code 补充测试：

```
运行覆盖率检查，找出覆盖率低于 90% 的模块，
并为这些模块补充测试。
```

## 第十章：TDD 工作流快速参考卡

### 提示词模板

| 场景 | 提示词 |
|------|--------|
| 启动新功能 TDD | `用 TDD 方式实现 [功能描述]。请先写测试，确认失败后再写实现。` |
| 继续下一轮 | `测试通过了。现在进入 Refactor 阶段，优化代码后重新运行测试。` |
| 补充测试 | `运行覆盖率检查，为覆盖率不足的模块补充测试。` |
| 修复失败测试 | `以下测试失败了：[粘贴输出]。请分析原因并修复实现代码。` |
| 批量 TDD | `使用 tdd-parallel skill，并行实现 [功能列表]。` |

### 常用命令速查

```bash
# Python
python -m pytest tests/ -v                    # 运行测试
python -m pytest tests/ --cov=src             # 覆盖率
python -m pytest tests/test_xxx.py -k "test_" # 运行特定测试
python -m pytest tests/ -x                    # 遇到第一个失败就停止

# Node.js
npx vitest run                                # 运行测试
npx vitest run --coverage                     # 覆盖率
npx vitest run tests/xxx.test.ts              # 运行特定测试
```

### TDD 诊断清单

当 TDD 流程出现问题时：

- [ ] CLAUDE.md 是否包含 TDD 规则？
- [ ] 测试是否能独立运行（不依赖外部状态）？
- [ ] 是否使用了 Skill 约束 Claude Code 的行为？
- [ ] 是否在每轮循环后运行了 `/compact`？
- [ ] 测试是否只测公共接口？
- [ ] 覆盖率是否达到目标？

## 延伸阅读

- [Claude Code 官方文档 — Common Workflows](https://code.claude.com/docs/en/common-workflows) — 官方推荐的 TDD 工作流
- [Claude Code Best Practices](https://code.claude.com/docs/en/best-practices) — 上下文管理和提示工程建议
- [TDD Skill for Claude Code (GitHub)](https://github.com/matlugert/tdd-skill) — 开源 TDD Skill 模板
- [Superpowers Plugin](https://pasqualepillitteri.it/en/news/215/superpowers-claude-code-complete-guide) — 内置 TDD Skill 的官方插件完整指南
- [Forcing Claude Code to TDD (alexop.dev)](https://alexop.dev/posts/custom-tdd-workflow-claude-code-vue/) — 自定义 Red-Green-Refactor 循环深度实战

## 参考来源

1. [Claude Code Common Workflows](https://code.claude.com/docs/en/common-workflows) — Anthropic 官方 TDD 工作流文档
2. [Forcing Claude Code to TDD: An Agentic Red-Green-Refactor Loop](https://alexop.dev/posts/custom-tdd-workflow-claude-code-vue/) — Skill 编排 Subagent 实现自定义 TDD 流程
3. [How to Implement TDD with Claude Code TDD Skill](https://docs.bswen.com/blog/2026-03-25-tdd-skill-claude-code/) — TDD Skill 配置与使用教程
4. [TDD Skill for Claude Code (GitHub)](https://github.com/matlugert/tdd-skill) — 开源 TDD Skill 模板仓库
5. [Claude Code Best Practices 2026](https://aiorg.dev/blog/claude-code-best-practices) — 6 个生产项目的 Claude Code 最佳实践

## 训练营适用性评估

| 维度 | 评估 |
|------|------|
| **适合训练营类型** | 后端开发训练营、全栈训练营、代码质量提升工作坊 |
| **预计教学时长** | 3-4 小时（含动手实操） |
| **难度级别** | 中级（需 Claude Code 基础 + 测试框架经验） |
| **前置课程** | Claude Code 入门篇 + Python/JS 基础测试 |
| **实操占比** | 70%（大量代码编写和测试运行） |
| **学员收获** | 掌握 AI 辅助 TDD 全流程，能独立在项目中实践 |

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
