TDD with AI:用测试驱动 AI 开发
传统 TDD 回顾
测试驱动开发(Test-Driven Development)由 Kent Beck 在极限编程中推广,核心循环只有三步:
| 阶段 | 颜色 | 动作 |
|---|---|---|
| Red | 红 | 写一个失败的测试 |
| Green | 绿 | 写最少的代码让测试通过 |
| Refactor | 蓝 | 重构代码,保持测试通过 |
这个循环每次迭代通常只有几分钟。它的价值不在于"先写测试"这件事本身,而在于强迫开发者在写代码之前,先清晰地定义"完成"的标准。
传统 TDD 的痛点是:写测试比写实现耗时,很多团队坚持不下来。AI 的出现彻底改变了这个等式。
Prompt-Test Driven Development(PTDD)
Eric Elliott 在其博客中提出:AI 时代的 TDD 应该叫做"Prompt-Test Driven Development"——用提示词(Prompt)驱动测试生成,再用测试驱动实现。
核心思路: 你写测试规格,AI 生成测试代码;你验证测试是否正确失败,AI 生成实现代码;你验证测试是否通过。
人的角色从"代码生产者"变成了需求定义者 + 质量把关人。
PTDD 工作流程
你:写测试规格(用自然语言描述期望行为)
↓
AI:生成测试代码
↓
你:运行测试 → 确认红灯(测试失败)
↓
AI:生成实现代码
↓
你:运行测试 → 确认绿灯(测试通过)
↓
你/AI:重构,测试保持绿灯关键步骤是第三步:必须亲自确认测试失败。跳过这步,你就不知道测试是否真的在测东西。
为什么 TDD 与 AI 特别契合
1. 快速反馈回路
AI 生成代码很快,但可能生成错误的代码。测试提供了即时的、客观的反馈:通过或失败,没有歧义。
2. 需求表达更清晰
给 AI 的提示词越具体,结果越好。测试规格迫使你把模糊需求转化为具体的输入输出对,这正好也是最好的 AI 提示词。
3. 捕获 AI 幻觉
AI 可能生成"看起来正确"但实际上有 bug 的代码。测试是捕获这类问题的安全网。没有测试,幻觉代码可能在生产环境才被发现。
4. 重构的信心
AI 经常需要重构或重写代码段。有测试覆盖,你可以放心让 AI 改写实现,只要测试保持绿灯。
实战示例:实现一个购物车折扣函数
第一步:写测试规格(自然语言)
函数 calculateDiscount(cartTotal, memberLevel):
- 普通用户('regular'):无折扣
- 银卡用户('silver'):消费满 200 打 95 折,否则无折扣
- 金卡用户('gold'):消费满 200 打 9 折,满 500 打 85 折
- 输入非法 memberLevel 时抛出 Error第二步:发给 AI,生成测试代码
AI 生成的测试(使用 Vitest):
typescript
import { describe, it, expect } from 'vitest'
import { calculateDiscount } from './discount'
describe('calculateDiscount', () => {
describe('普通用户', () => {
it('任何金额均无折扣', () => {
expect(calculateDiscount(100, 'regular')).toBe(100)
expect(calculateDiscount(500, 'regular')).toBe(500)
})
})
describe('银卡用户', () => {
it('消费不足 200 时无折扣', () => {
expect(calculateDiscount(199, 'silver')).toBe(199)
})
it('消费满 200 时打 95 折', () => {
expect(calculateDiscount(200, 'silver')).toBe(190)
expect(calculateDiscount(300, 'silver')).toBe(285)
})
})
describe('金卡用户', () => {
it('消费不足 200 时无折扣', () => {
expect(calculateDiscount(100, 'gold')).toBe(100)
})
it('消费满 200 不足 500 时打 9 折', () => {
expect(calculateDiscount(200, 'gold')).toBe(180)
expect(calculateDiscount(499, 'gold')).toBeCloseTo(449.1)
})
it('消费满 500 时打 85 折', () => {
expect(calculateDiscount(500, 'gold')).toBe(425)
expect(calculateDiscount(1000, 'gold')).toBe(850)
})
})
describe('非法输入', () => {
it('非法会员等级应抛出 Error', () => {
expect(() => calculateDiscount(100, 'vip')).toThrow()
expect(() => calculateDiscount(100, '')).toThrow()
})
})
})第三步:确认红灯
bash
$ npx vitest run discount.test.ts
FAIL src/discount.test.ts
● calculateDiscount › 普通用户 › 任何金额均无折扣
Cannot find module './discount'此时 discount.ts 不存在,测试正确地失败了。
第四步:让 AI 生成实现
提示词:
根据上面的测试,实现 calculateDiscount 函数。
使用 TypeScript,导出具名函数。AI 生成的实现:
typescript
type MemberLevel = 'regular' | 'silver' | 'gold'
export function calculateDiscount(cartTotal: number, memberLevel: string): number {
switch (memberLevel as MemberLevel) {
case 'regular':
return cartTotal
case 'silver':
if (cartTotal >= 200) return cartTotal * 0.95
return cartTotal
case 'gold':
if (cartTotal >= 500) return cartTotal * 0.85
if (cartTotal >= 200) return cartTotal * 0.9
return cartTotal
default:
throw new Error(`未知的会员等级: ${memberLevel}`)
}
}第五步:确认绿灯
bash
$ npx vitest run discount.test.ts
✓ src/discount.test.ts (8 tests)
Test Files 1 passed (1)
Tests 8 passed (8)
Duration 312ms所有测试通过。
PTDD 的注意事项
检查 AI 生成的测试是否在测正确的事情
有时 AI 会写出"永远通过"的测试,例如:
typescript
// 这个测试没有实际意义
it('应该返回结果', () => {
const result = calculateDiscount(100, 'regular')
expect(result).toBeDefined() // 只要不是 undefined 就通过
})你需要审查每个断言,确认它真的在验证业务规则。
不要跳过红灯步骤
这是最容易被省略的一步,也是最重要的一步。没有红灯,你不知道测试是否在测任何东西。
测试规格要具体
模糊的规格产生模糊的测试。"应该正确计算折扣"不如"金卡用户消费 500 元应返回 425 元"。
总结
TDD 与 AI 的结合不是"让 AI 写测试然后完事",而是一个人机协作的质量保障循环。你负责定义正确性标准,AI 负责生成代码,测试负责客观验证。三者缺一不可。