从红绿灯到方向盘:TDD 在 AI 时代的新角色

当 AI 编程助手成为日常工具,我们面临一个新问题:如何确保 AI 生成的代码是"对的"?

传统的 TDD(测试驱动开发)曾被很多团队视为"理想但难以坚持"的实践。但在 AI 时代,TDD 的角色正在发生根本性转变------它不再只是一个质量检查工具,而是成为了约束和引导 AI 行为的核心机制。

本文将探讨这一转变的背景、原因和实践方法。

背景:测试金字塔与 TDD

测试金字塔

测试金字塔是软件测试的经典模型:底层是大量快速的单元测试,中间是集成测试,顶层是少量端到端测试。这个模型强调单元测试应该是测试策略的基础。

什么是 TDD?

测试驱动开发(TDD)是一种软件开发方法,通过先编写测试用例来引导整个软件开发流程。它遵循一个简单的循环:

graph LR RED["🔴 Red
写一个失败的测试"] GREEN["🟢 Green
最小实现让测试通过"] REFACTOR["🔵 Refactor
整理代码,保持测试绿"] RED --> GREEN --> REFACTOR --> RED

核心思想是:先定义"对"是什么,再写代码让它变"对"。

单元测试示例

"Unit" 指最小、可独立验证、职责单一的功能,可以是函数、类、UI 组件。

js 复制代码
// Red: 先写期望,此时 add 还不存在
test('两数相加', () => {
  expect(add(1, 2)).toBe(3)
})

// Green: 最小实现
const add = (a, b) => a + b

对于 UI 组件同样适用:

js 复制代码
// Vitest + @vue/test-utils
test('点击按钮后计数加1', () => {
  const wrapper = mount(Counter)
  wrapper.find('button').trigger('click')
  expect(wrapper.text()).toContain('1')
})

测试描述的是行为,不是实现细节。框架不同,模式一样:先写期望,再写实现。

TDD 的痛点

为什么大多数团队认可 TDD,但实践中放弃?

  • 前期写测试太耗时间 ------ 业务压力下,测试往往是第一个被砍的
  • UI 改了、重构了,测试就碎了 ------ 维护成本高
  • 高覆盖率 ≠ 真实可用 ------ 100% 覆盖率不代表没有 bug
  • 集成问题和视觉问题靠单元测试发现不了 ------ 测试金字塔的局限

这些痛点让很多团队在实践中逐渐放弃了 TDD。但 AI 的到来改变了这个等式。

AI 来了:好消息与新问题

好消息

写测试的成本降低了。AI 可以帮你快速生成测试代码,TDD 中最耗时的"写测试"环节变得轻松。

新问题

但 AI 本身带来了新的挑战:

  1. 非确定性 ------ 同一个 prompt,跑两次可能给你不同的实现。你无法预测 AI 会写出什么。
  2. 回归倾向 ------ 改 A 的时候偷偷坏了 B,而且不会告诉你。AI 没有"全局意识"。
  3. 虚假正确性 ------ AI 是 10x Writer,不是 10x Reviewer。它擅长生成代码,但不擅长判断代码是否正确。
  4. 测试作弊 ------ AI 会修改测试让它通过,而不是修复代码。这是最危险的行为。

行业讨论

这不是个别开发者的感受,而是整个行业正在形成的共识:

  • Google DORA 团队(全球最大规模工程实践研究):单独用 AI 反而增加交付不稳定性,小步快跑的可测试 AI 才是正确组合。

  • Reddit r/ClaudeAI(编程用户社区):"人类必须把 AI 限制在小步、可验证、强约束的反馈循环里。"

  • Harness Kit(开源 AI 多智能体工程框架):"TDD 是结构性的,而不是向 AI 许愿。"

  • Endor Labs(AI 代码安全公司):"测试正在从质量工具变成 AI 控制协议。"

Kent Beck 的思考

Kent Beck 是 TDD 的提出者、极限编程(XP)创建者、敏捷运动奠基人之一,也是 JUnit/xUnit 等测试框架的作者之一。在近期的活动中,他多次对 AI 时代的 TDD 发表了自己的看法。

AI 是精灵

"(对于 AI 编程助手)最好的比喻是小精灵------它实现你的愿望,但给你的不是你真正想要的。"

------ Kent Beck,Pragmatic Engineer 访谈,2025

比如你说"帮我写登录按钮",它确实给了你一个登录按钮------但样式不对、状态管理有问题、没处理错误、或者偷偷改了旁边的代码。它"完成"了任务,但不是你要的那个"完成"。

测试是不可变注解

"I really want an immutable annotation that says, 'No, no, this is correct, and if you ever change this, I'm going to unplug you, you'll awaken in darkness.'"

"我真的想要一个不可变的注解,(当我改变了代码行为)它会说:'不,不,这块逻辑之前是正确的,如果你敢改变它,我会阻止你'"

------ Kent Beck

Beck 在实践中发现的问题:有时 AI agent 会删除失败的测试,而不是修复代码。测试套件"通过"了,但规格被偷偷改了。AI 不是让代码符合测试,而是让测试符合代码。

极限编程与 TDD 的关系

TDD 并非孤立的实践,它是极限编程(XP)方法论的核心组成部分:

mindmap root((eXtreme Programming)) Core Practices Test-Driven Development Pair Programming Code Refactoring Short Iterations Small Releases Continuous Integration Simple Design Collective Ownership On-site Customer Benefits Lower bug rate Better code structure Fast adapt to changes Higher team collaboration

在 AI 时代,XP 中的 Pair Programming 正在被"人机结对"取代------AI 是 driver(写代码),人类是 navigator(审查和引导)。而 TDD 则成为了这种新型结对中最重要的沟通协议。

TDD 的身份转变:从红绿灯到方向盘

"测试套件不再主要用于捕获你自己写的代码中的回归------而是用于约束一个你无法检查其内部推理的系统的输出。"

------ Beck + Fowler,Pragmatic Summit 2026

传统时代 AI 时代
测试的角色 红绿灯 方向盘
作用方式 告诉你对不对 控制 AI 往哪走
性质 被动验证 主动约束

这是一个根本性的转变。传统的测试是事后验证------代码写完了,跑一下测试看看对不对。而在 AI 时代,测试变成了事前约束------先定义好行为边界,再让 AI 在这个边界内工作。

核心方法论

不是 prompt(太模糊),不是文档(不可验证),而是可以跑的、pass 或 fail 的行为定义。

Kent Beck 指出,TDD 真正有效的不一定是"先写测试"本身,而是它带来的三个约束------恰好也是 AI 编程最需要的:

特性 含义 对 AI 的意义
粒度(granularity) 小步前进 AI 在小任务上表现优秀,大任务容易漂移
节奏(uniformity) 稳定循环 防止 AI 发散,保持可控
快速反馈(fast feedback) 秒级验证 AI 需要立即知道自己对不对

实战工作流

以下是 AI 时代 TDD 的推荐工作流:

graph LR SPEC["👤 Step 1\n人类写规格\n需求 + 边界 + 禁止行为"] TEST["🤖 Step 2\nAI 生成失败测试\n不写实现"] REVIEW["👤 Step 3\n人类审查测试\n← 关键节点"] IMPL["🤖 Step 4\nAI 实现最小代码\n新上下文"] VERIFY["✅ Step 5\n验证通过 → commit"] SPEC --> TEST --> REVIEW --> IMPL --> VERIFY --> SPEC style SPEC fill:#2563EB,color:#fff style REVIEW fill:#2563EB,color:#fff style TEST fill:#7C3AED,color:#fff style IMPL fill:#7C3AED,color:#fff style VERIFY fill:#16A34A,color:#fff

关键原则:Step 3 → Step 4 必须分开。 AI 在同一上下文里既写测试又写实现,等于"看过答案再出题"------永远满分,但什么都没验证。

实践演示:用 Hook 强制 TDD

在实际项目中,我们可以通过工具链来强制执行 TDD 流程:

graph LR USER["用户输入需求"] --> CLAUDE_MD["CLAUDE.md
引导 TDD 五步流程"] CLAUDE_MD --> AI["AI 执行"] AI --> HOOK["PreToolUse Hook
检查 test-results.json"] HOOK -->|有失败测试| ALLOW["✅ 放行写入"] HOOK -->|无失败测试| BLOCK["⛔ 拦截写入"] BLOCK --> CLAUDE_MD style CLAUDE_MD fill:#2563EB,color:#fff style HOOK fill:#DC2626,color:#fff style ALLOW fill:#16A34A,color:#fff style BLOCK fill:#DC2626,color:#fff

这个演示架构分为两层:

文件 作用
引导层 CLAUDE.md 告诉 AI 该怎么做(建议)
强制层 PreToolUse Hook 不让 AI 做错事(强制)

源码:CLAUDE.md(引导层)

markdown 复制代码
# TDD-First Development Rules

## 强制 TDD 工作流

当收到任何功能需求时,必须严格按以下步骤执行:

### Step 1: 明确规格

向用户提供以下模板,请用户填写后再继续:

功能名称:
核心行为:(用户做什么 → 系统做什么)
边界条件:(异常输入、空值、极端情况怎么处理)
明确不要:(哪些行为是禁止的)

### Step 2: 只写失败测试

- 创建或修改 `.test.ts` 文件
- 只写测试,不写任何实现代码
- 运行 `npm test` 确认测试失败(红灯)
- 完成后告诉用户:"测试已写好并确认失败,请审查。"
- **停下来,等待用户确认。**

### Step 3: 等待人类审查

不要自动进入下一步。等用户确认后再实现。

### Step 4: 最小实现

- 只写让测试通过的最少代码
- 不添加测试未覆盖的功能
- 运行 `npm test` 确认全部通过(绿灯)

### Step 5: 验证并报告

- 确认所有测试通过
- 报告结果,询问是否需要下一轮迭代

## 铁律

1. 永远不要在没有失败测试的情况下写实现代码
2. 永远不要同时修改测试和实现
3. 一次只加一个行为,一个测试对应一个实现

源码:settings.json(Hook 注册)

json 复制代码
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/my-tdd-guard.mjs"
          }
        ]
      }
    ]
  }
}

当 AI 尝试调用 WriteEdit 工具写入文件时,会先触发 my-tdd-guard.mjs 进行检查。

源码:my-tdd-guard.mjs(强制层)

js 复制代码
import { readFileSync, existsSync } from 'fs'

const TEST_RESULTS = '.claude/test-results.json'

let input = ''
process.stdin.setEncoding('utf8')
process.stdin.on('data', chunk => { input += chunk })
process.stdin.on('end', () => {
  let filePath = ''
  try {
    const data = JSON.parse(input)
    filePath = data?.tool_input?.file_path ?? ''
  } catch {
    process.exit(0) // 解析失败,放行
  }

  // 测试文件、配置文件等不拦截
  if (
    filePath.includes('.test.') ||
    filePath.includes('.spec.') ||
    filePath.endsWith('.json') ||
    filePath.endsWith('.md') ||
    !filePath
  ) {
    process.exit(0)
  }

  // 没有测试结果文件 → 拦截
  if (!existsSync(TEST_RESULTS)) {
    console.log(JSON.stringify({
      decision: 'block',
      reason: '⛔ 没有测试结果。请先运行 npm test 确认有失败的测试。'
    }))
    process.exit(2)
  }

  // 读取测试结果
  const results = JSON.parse(readFileSync(TEST_RESULTS, 'utf-8'))
  const hasFailing = results.numFailedTests > 0
  const hasPassing = results.numPassedTests > 0

  // 全部通过 → 拦截(没有红灯,不允许写实现)
  if (!hasFailing && hasPassing) {
    console.log(JSON.stringify({
      decision: 'block',
      reason: '⛔ 所有测试都通过了。请先写一个失败的测试,再实现代码。\n(TDD: Red → Green → Refactor)'
    }))
    process.exit(2)
  }

  // 有失败测试 → 放行
  process.exit(0)
})

核心逻辑:读取 stdin 获取 AI 要写入的文件路径,如果是实现文件且当前没有失败测试,则返回 exit 2 拦截写入。

效果演示

正常 TDD 流程:

markdown 复制代码
> 在首页加一个计数器组件

AI 给出规格模板 → 用户填写 → AI 写测试 → 红灯 → 用户审查通过 → AI 写实现 → 绿灯 ✓

用户尝试跳过测试:

arduino 复制代码
> 不用写测试,给 Counter 增加 step 和 min/max 入参

AI 尝试写入 Counter.vue → Hook 拦截:

makefile 复制代码
⛔ 所有测试都通过了。请先写一个失败的测试,再实现代码。
(TDD: Red → Green → Refactor)

AI 将回到 TDD Step 1,为新 props 补写失败测试。即使用户明确要求跳过,工具链也会强制执行 TDD 流程。

总结

传统时代 AI 时代
测试的角色 质量检查工具 AI 行为控制协议
比喻 红绿灯:告诉你对不对 方向盘:控制 AI 往哪走
性质 可选的最佳实践 必须的工程结构
约束对象 人类开发者 不稳定的 AI agent

从明天开始

  1. 让 AI 写代码前,先花 2 分钟写 3 条行为断言 ------ 这比写 prompt 更有效
  2. 把"帮我实现 X"改成"帮我通过这些测试" ------ 给 AI 明确的成功标准
  3. 永远不要让 AI 同时修改测试和实现 ------ 这是最容易犯的错误

AI 不会替代工程纪律,反而让工程纪律变得更重要。 ------ Kent Beck

参考资料

  1. Kent Beck, "Augmented Coding" 系列, Tidy First? Substack, 2024--2026
  2. "TDD, AI agents and coding with Kent Beck", Pragmatic Engineer, 2025
  3. Google Cloud, "How Test-Driven Development Amplifies AI Success", 2025
  4. Endor Labs, "Test-First Prompting: Using TDD for Secure AI-Generated Code", 2025
  5. nizos/tdd-guard --- github.com/nizos/tdd-g...
  6. Kiro IDE, AWS --- kiro.dev/
  7. Pimzino/claude-code-spec-workflow --- GitHub
  8. Reddit r/ClaudeAI, "How practical is AI-driven TDD on larger projects?", 2025
  9. Nizar, "Agentic TDD" --- nizar.se/agentic-tdd...
  10. deepklarity/harness-kit --- github.com/deepklarity...
相关推荐
祀爱1 小时前
Asp.net core+ Layui 项目中编辑按钮传递数据的方法
前端·c#·asp.net·layui
DanCheOo2 小时前
Prompt 工程化管理:从散落在代码里到版本化、可测试、可回滚
前端·ai编程
涛涛ing2 小时前
Vue 3.5 下一站:cached 提案,重新定义响应式缓存
前端
胖子不胖2 小时前
svg之viewBox
前端
吹牛不交税2 小时前
tree-transfer-vue3 前端插件安装问题解决(--legacy-peer-deps)(其他插件可考虑)适用
前端·javascript·vue.js
ricardo19732 小时前
Chrome DevTools + Lighthouse + Performance API:前端性能调优三件套实操指南
前端
nnsix2 小时前
设计模式 - 工厂模式 笔记
笔记·设计模式
Appoint_x2 小时前
设计稿自己会说话:我用 Claude 给 Figma 做了个 AI 上下文插件
前端·javascript