本教程深入讲解如何在 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 语法,知道什么是单元测试 |
测试驱动开发的核心循环是 Red → Green → Refactor:
传统 TDD 最大的痛点在于——写测试比写实现更难。开发者需要先思考接口设计,再描述期望行为,这对心智负担要求很高。结果是,大多数人在压力下跳过测试,直接写实现。
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
# 创建项目目录
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
在项目根目录创建 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 行为。
创建 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 |
在引入 Skill 自动化之前,先通过手动对话理解 Claude Code 的 TDD 节奏。
在 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 类还不存在)
现在写最少的实现代码,让测试通过。不要添加任何额外功能。
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
测试全部通过了。请重构代码,提升可读性和可维护性,
但不要改变任何外部行为。重构后再运行测试确认。
重构后的代码:
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 将这个流程结构化,让 Claude Code 自动循环。
Superpowers 是 Claude Code 的官方推荐插件,内置 TDD Skill:
# 通过 Claude Code 的 /install-github-plugin 命令安装
claude /install-github-plugin anthropics/superpowers
安装后,TDD Skill 位于 ~/.claude/skills/superpowers/test-driven-development/。
方式一:通过 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%
当你调用 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 实现并行测试。
创建 .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)
- 覆盖率
- 重构记录
在实际操作中,你可以用这样的提示词驱动 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 是 Claude Code 的事件钩子系统,可以在关键时刻自动运行测试,防止 TDD 纪律被打破。
创建 .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:修改文件后,自动运行测试并显示结果。
{
"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 提醒:先写测试,再写实现'"
}
]
}
]
}
}
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 全流程。我们将构建一个 FastAPI 应用的请求数据验证层。
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
# 项目规范
## 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 风格
输入 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%
继续输入:
现在用 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
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 项目,TDD 流程同样适用。以下是 Vitest + TypeScript 的快速指南。
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`
测试 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
TDD 会产生大量文件(测试 + 实现),上下文窗口容易溢出。解决方案:
策略 1:分层测试,逐步聚焦
# 不要一次性把所有测试都塞进上下文
# 先写核心功能的测试,实现通过后,再添加边界测试
第一步:请为 User 类的核心功能(创建、序列化)写测试
# ... 实现通过 ...
第二步:现在添加边界条件测试(空值、超长字符串、特殊字符)
策略 2:使用 /compact 压缩上下文
每完成一轮 Red-Green-Refactor 后,运行 /compact 压缩对话历史,释放上下文空间。
策略 3:用 CLAUDE.md 管理测试约定
## 测试约定
- 每个模块一个测试文件
- 测试类按功能分组,不要一个类超过10个测试方法
- 使用 pytest.mark.parametrize 批量测试相似场景
- fixture 放在 conftest.py 中共享
| 反模式 | 描述 | 解决方案 |
|---|---|---|
| 测试后置 | Claude Code 先写实现再补测试 | 在 CLAUDE.md 中强调"永远先写测试" |
| 过度测试 | 为每个 getter/setter 写测试 | 只测业务逻辑,不测框架行为 |
| 脆弱测试 | 测试依赖实现细节 | 测试公共接口,不测私有方法 |
| 批量跳步 | 一次性生成所有测试和实现 | 用 Skill 强制逐步执行 |
| 忽略 Red | 跳过"确认测试失败"的步骤 | 在 Skill 中要求先运行测试确认失败 |
对于涉及多个文件的功能(如前后端联调),使用分批策略:
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 循环。
在 CLAUDE.md 中设置覆盖率目标:
## 覆盖率要求
- 行覆盖率:≥ 90%
- 分支覆盖率:≥ 80%
- 每次实现新功能后,运行覆盖率检查
- 如果覆盖率不足,优先补充边界条件测试
然后用这条提示词让 Claude Code 补充测试:
运行覆盖率检查,找出覆盖率低于 90% 的模块,
并为这些模块补充测试。
| 场景 | 提示词 |
|---|---|
| 启动新功能 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 流程出现问题时:
/compact?| 维度 | 评估 |
|---|---|
| 适合训练营类型 | 后端开发训练营、全栈训练营、代码质量提升工作坊 |
| 预计教学时长 | 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 等场景全覆盖。
