跳转到内容

TDD 测试驱动开发

测试驱动开发(TDD)要求先写测试、再写实现。但 Claude 的自然倾向是先写实现、再补测试。要用好 TDD,你必须明确指示 Claude 先写失败测试。


红 → 绿 → 重构
关键:你必须明确提示 Claude:
"写一个会失败的测试来验证 [功能]。不要写实现代码。"

在项目的 CLAUDE.md 中添加以下规则,让 Claude 默认遵循 TDD:

## 测试规范
### TDD 工作流
- 总是在实现之前写失败测试
- 使用 AAA 模式:Arrange-Act-Assert
- 尽量一个测试一个断言
- 测试名称描述行为:"should_return_empty_when_no_items"
### 测试优先规则
- 当我要求一个功能时,先写测试
- 测试应该在初始状态下失败(因为实现不存在)
- 只有测试写好后,才实现最小代码使其通过

  1. 向 Claude 明确描述要测试的功能
  2. 强调不要写实现代码
  3. Claude 创建测试文件,引用尚不存在的函数
  4. 运行测试,确认它们失败

提示词模板

写一个会失败的测试来验证:
一个计算购物车总价的函数,如果总价超过 100 元打 9 折。
不要实现该函数。

验证

Terminal window
npm test # 应该失败:calculateCartTotal is not defined

提示词模板

现在实现最少的代码让这些测试通过。
只写刚好能通过当前测试的代码,不要多写。

Claude 应该:

  • 创建实现文件
  • 写最小代码满足测试
  • 避免过度工程化

验证

Terminal window
npm test # 应该全部通过

提示词模板

重构实现以提高代码质量。
重构后测试必须保持通过。
重点关注:可读性 / 性能 / 消除重复

Claude 应该:

  • 改善代码但不改变行为
  • 运行测试确认仍然通过
  • 记录重大变更

用 TDD 方式实现一个 URL 短链服务。
首先,写失败测试来验证:
1. 缩短 URL 返回一个短码
2. 用短码可以取回原始 URL
3. 无效 URL 会被拒绝
4. 过期链接返回错误
不要实现任何东西。
测试已写好且全部失败。现在实现最少的代码让它们通过。
暂时用内存存储。
测试通过了。现在重构:
- 将 URL 验证提取为独立函数
- 添加适当的错误类型
- 改善变量命名
每次修改后运行测试确保仍然通过。

使用计划模式来规划测试策略:

[按 Shift+Tab 进入计划模式]
我需要用 TDD 实现一个购物车。
在写任何代码之前先规划测试用例。

Claude 会以只读模式探索代码库,然后在实现前提出测试计划。

.claude/settings.json 中配置 PostToolUse hook,编辑后自动运行测试:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": "npm test --watchAll=false 2>&1 | head -20"
}
]
}
}

将测试编写委托给专注的 Agent:

使用 test-writer agent 为 UserService 类创建全面的测试,
覆盖所有边缘情况。然后我来实现使测试通过。

反模式为什么错正确做法
”给这个功能写测试”Claude 会先实现再测试”写失败测试,实现尚不存在"
"同时写测试和实现”失去了测试优先的好处拆成两个提示词
”确保测试通过”鼓励先实现再测试”先写测试,再最小化实现”
跳过重构阶段累积技术债务绿灯后一定要重构
一次多个功能失去焦点一个 TDD 循环对应一个功能
# 给现有的 calculateTotal 函数写测试
"为 calculateTotal 函数写测试"
# 同时写测试和实现
"实现 calculateTotal 并带上测试"

为排序函数写基于属性的测试。
要测试的属性:
- 输出长度等于输入长度
- 输入中的所有元素都在输出中
- 输出是有序的
使用 fast-check 或类似的库。
测试通过后,运行变异测试找出薄弱点。
找出无法捕获变异的测试。
我需要重构 legacyFunction。
首先,写表征测试来捕获当前行为。
然后我们可以放心地重构。