TDD 测试驱动开发
测试驱动开发(TDD)要求先写测试、再写实现。但 Claude 的自然倾向是先写实现、再补测试。要用好 TDD,你必须明确指示 Claude 先写失败测试。
红 → 绿 → 重构
关键:你必须明确提示 Claude:"写一个会失败的测试来验证 [功能]。不要写实现代码。"配置 CLAUDE.md
Section titled “配置 CLAUDE.md”在项目的 CLAUDE.md 中添加以下规则,让 Claude 默认遵循 TDD:
## 测试规范
### TDD 工作流- 总是在实现之前写失败测试- 使用 AAA 模式:Arrange-Act-Assert- 尽量一个测试一个断言- 测试名称描述行为:"should_return_empty_when_no_items"
### 测试优先规则- 当我要求一个功能时,先写测试- 测试应该在初始状态下失败(因为实现不存在)- 只有测试写好后,才实现最小代码使其通过红-绿-重构循环
Section titled “红-绿-重构循环”阶段 1:红(写失败测试)
Section titled “阶段 1:红(写失败测试)”- 向 Claude 明确描述要测试的功能
- 强调不要写实现代码
- Claude 创建测试文件,引用尚不存在的函数
- 运行测试,确认它们失败
提示词模板:
写一个会失败的测试来验证:一个计算购物车总价的函数,如果总价超过 100 元打 9 折。不要实现该函数。验证:
npm test # 应该失败:calculateCartTotal is not defined阶段 2:绿(最小实现)
Section titled “阶段 2:绿(最小实现)”提示词模板:
现在实现最少的代码让这些测试通过。只写刚好能通过当前测试的代码,不要多写。Claude 应该:
- 创建实现文件
- 写最小代码满足测试
- 避免过度工程化
验证:
npm test # 应该全部通过阶段 3:重构(清理代码)
Section titled “阶段 3:重构(清理代码)”提示词模板:
重构实现以提高代码质量。重构后测试必须保持通过。重点关注:可读性 / 性能 / 消除重复Claude 应该:
- 改善代码但不改变行为
- 运行测试确认仍然通过
- 记录重大变更
完整示例:URL 短链服务
Section titled “完整示例:URL 短链服务”第一步:红 — 写失败测试
Section titled “第一步:红 — 写失败测试”用 TDD 方式实现一个 URL 短链服务。首先,写失败测试来验证:1. 缩短 URL 返回一个短码2. 用短码可以取回原始 URL3. 无效 URL 会被拒绝4. 过期链接返回错误
不要实现任何东西。第二步:绿 — 最小实现
Section titled “第二步:绿 — 最小实现”测试已写好且全部失败。现在实现最少的代码让它们通过。暂时用内存存储。第三步:重构 — 清理代码
Section titled “第三步:重构 — 清理代码”测试通过了。现在重构:- 将 URL 验证提取为独立函数- 添加适当的错误类型- 改善变量命名
每次修改后运行测试确保仍然通过。与 Claude Code 功能集成
Section titled “与 Claude Code 功能集成”配合计划模式
Section titled “配合计划模式”使用计划模式来规划测试策略:
[按 Shift+Tab 进入计划模式]
我需要用 TDD 实现一个购物车。在写任何代码之前先规划测试用例。Claude 会以只读模式探索代码库,然后在实现前提出测试计划。
配合 Hooks 自动运行测试
Section titled “配合 Hooks 自动运行测试”在 .claude/settings.json 中配置 PostToolUse hook,编辑后自动运行测试:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "command": "npm test --watchAll=false 2>&1 | head -20" } ] }}配合子 Agent
Section titled “配合子 Agent”将测试编写委托给专注的 Agent:
使用 test-writer agent 为 UserService 类创建全面的测试,覆盖所有边缘情况。然后我来实现使测试通过。反模式:避免踩坑
Section titled “反模式:避免踩坑”| 反模式 | 为什么错 | 正确做法 |
|---|---|---|
| ”给这个功能写测试” | Claude 会先实现再测试 | ”写失败测试,实现尚不存在" |
| "同时写测试和实现” | 失去了测试优先的好处 | 拆成两个提示词 |
| ”确保测试通过” | 鼓励先实现再测试 | ”先写测试,再最小化实现” |
| 跳过重构阶段 | 累积技术债务 | 绿灯后一定要重构 |
| 一次多个功能 | 失去焦点 | 一个 TDD 循环对应一个功能 |
# 给现有的 calculateTotal 函数写测试"为 calculateTotal 函数写测试"
# 同时写测试和实现"实现 calculateTotal 并带上测试"# 假设函数不存在"为 calculateTotal 的行为写测试,假设函数不存在。然后我们验证现有实现是否通过。"
# 分两步"为 calculateTotal 写失败测试。到此为止。"[测试写好后]"现在实现使这些测试通过。"基于属性的测试
Section titled “基于属性的测试”为排序函数写基于属性的测试。要测试的属性:- 输出长度等于输入长度- 输入中的所有元素都在输出中- 输出是有序的使用 fast-check 或类似的库。测试通过后,运行变异测试找出薄弱点。找出无法捕获变异的测试。遗留代码的 TDD
Section titled “遗留代码的 TDD”我需要重构 legacyFunction。首先,写表征测试来捕获当前行为。然后我们可以放心地重构。