Skip to content

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 负责生成代码,测试负责客观验证。三者缺一不可。