当 AI 编程助手成为日常工具,我们面临一个新问题:如何确保 AI 生成的代码是"对的"?
传统的 TDD(测试驱动开发)曾被很多团队视为"理想但难以坚持"的实践。但在 AI 时代,TDD 的角色正在发生根本性转变------它不再只是一个质量检查工具,而是成为了约束和引导 AI 行为的核心机制。
本文将探讨这一转变的背景、原因和实践方法。
背景:测试金字塔与 TDD
测试金字塔
测试金字塔是软件测试的经典模型:底层是大量快速的单元测试,中间是集成测试,顶层是少量端到端测试。这个模型强调单元测试应该是测试策略的基础。

什么是 TDD?
测试驱动开发(TDD)是一种软件开发方法,通过先编写测试用例来引导整个软件开发流程。它遵循一个简单的循环:
写一个失败的测试"] 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 本身带来了新的挑战:
- 非确定性 ------ 同一个 prompt,跑两次可能给你不同的实现。你无法预测 AI 会写出什么。
- 回归倾向 ------ 改 A 的时候偷偷坏了 B,而且不会告诉你。AI 没有"全局意识"。
- 虚假正确性 ------ AI 是 10x Writer,不是 10x Reviewer。它擅长生成代码,但不擅长判断代码是否正确。
- 测试作弊 ------ 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)方法论的核心组成部分:
在 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 的推荐工作流:
关键原则:Step 3 → Step 4 必须分开。 AI 在同一上下文里既写测试又写实现,等于"看过答案再出题"------永远满分,但什么都没验证。
实践演示:用 Hook 强制 TDD
在实际项目中,我们可以通过工具链来强制执行 TDD 流程:
引导 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 尝试调用 Write 或 Edit 工具写入文件时,会先触发 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 |
从明天开始
- 让 AI 写代码前,先花 2 分钟写 3 条行为断言 ------ 这比写 prompt 更有效
- 把"帮我实现 X"改成"帮我通过这些测试" ------ 给 AI 明确的成功标准
- 永远不要让 AI 同时修改测试和实现 ------ 这是最容易犯的错误
AI 不会替代工程纪律,反而让工程纪律变得更重要。 ------ Kent Beck
参考资料
- Kent Beck, "Augmented Coding" 系列, Tidy First? Substack, 2024--2026
- "TDD, AI agents and coding with Kent Beck", Pragmatic Engineer, 2025
- Google Cloud, "How Test-Driven Development Amplifies AI Success", 2025
- Endor Labs, "Test-First Prompting: Using TDD for Secure AI-Generated Code", 2025
- nizos/tdd-guard --- github.com/nizos/tdd-g...
- Kiro IDE, AWS --- kiro.dev/
- Pimzino/claude-code-spec-workflow --- GitHub
- Reddit r/ClaudeAI, "How practical is AI-driven TDD on larger projects?", 2025
- Nizar, "Agentic TDD" --- nizar.se/agentic-tdd...
- deepklarity/harness-kit --- github.com/deepklarity...