首页 › tutorials › 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 流程。这正是本教程要解决的核心问题。

整体工作流架构

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 项目

# 创建项目目录
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 约束规则:

# 项目规范

## 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:

[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:

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

运行测试,确认全部失败:

$ python -m pytest tests/ -v
# 预期:7 FAILED(因为 PasswordValidator 类还不存在)

3.2 第二轮:Green(最小实现)

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

Claude Code 生成 src/password_validator.py:

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

运行测试,确认全部通过:

$ python -m pytest tests/ -v
# 预期:7 passed

3.3 第三轮:Refactor(优化)

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

重构后的代码:

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
        )
$ 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:

# 通过 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 循环:

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 中添加:

## 开发流程

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

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

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

---
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:

---
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 会自动将功能拆分为可并行的分组:

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:

{
  "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 — 区分测试文件和实现文件

{
  "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 工作流全貌

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 项目初始化

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 配置

# 项目规范

## 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:

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("[email protected]")
        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": "[email protected]",
            "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

运行测试 — 全部失败:

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

输入 Claude Code:

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

Claude Code 生成 src/validators.py:

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)

运行测试 — 全部通过:

$ 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:

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": "[email protected]",
            "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:

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}
    )
$ 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 项目初始化

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

CLAUDE.md 配置:

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

8.2 示例:TDD 构建 URL 解析器

测试 tests/url-parser.test.ts:

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:

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}`)
    }
  }
}
$ npx vitest run
# ✓ UrlParser (4 tests) 4 passed

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

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

TDD 会产生大量文件(测试 + 实现),上下文窗口容易溢出。解决方案:

策略 1:分层测试,逐步聚焦

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

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

第二步:现在添加边界条件测试(空值、超长字符串、特殊字符)

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

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

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

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

9.2 避免 Claude Code 的 TDD 反模式

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

9.3 多文件 TDD 的策略

对于涉及多个文件的功能(如前后端联调),使用分批策略:

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 中设置覆盖率目标:

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

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

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

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

提示词模板

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

常用命令速查

# 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 流程出现问题时:

延伸阅读

参考来源

  1. Claude Code Common Workflows — Anthropic 官方 TDD 工作流文档
  2. Forcing Claude Code to TDD: An Agentic Red-Green-Refactor Loop — Skill 编排 Subagent 实现自定义 TDD 流程
  3. How to Implement TDD with Claude Code TDD Skill — TDD Skill 配置与使用教程
  4. TDD Skill for Claude Code (GitHub) — 开源 TDD Skill 模板仓库
  5. Claude Code Best Practices 2026 — 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学通信知识星球海报-扫码加入