乐于分享
好东西不私藏

让 AI 写代码时遵守 TDD?实验结果:更贵,还可能更差

让 AI 写代码时遵守 TDD?实验结果:更贵,还可能更差
Birgitta Böckeler 是 Thoughtworks 的 Distinguished Engineer(杰出工程师),也是这家咨询公司 AI 辅助交付业务的全球负责人。她是测试驱动开发(TDD)的忠实信徒,写过关于变异测试(mutation testing)的经典文章——变异测试是比覆盖率更苛刻的测试质量检验手段——还写过《给编码 agent 装传感器》这类把软件工程纪律迁移到 AI 时代的前沿文章。但最近,这位信徒亲手做了一场「拆自己台」的实验:她把 TDD 指令写进编码 agent(智能体)的提示词里,让 AI 严格按 TDD 流程写代码,再用另一个 AI 盲评打分。

结果让她自己都意外:5 个任务里,非 TDD 方案大多数时候排前两名,TDD 方案多排在后两位;测试质量(变异分)没有差别;token 成本却至少贵 3 倍,small 任务甚至贵 7 到 10 倍。

实验发布后,这篇文章在 martinfowler.com 上引发了不少讨论——不是因为结论反直觉,而是因为它把「AI 该不该用 TDD」这个吵了一年多的口水仗,第一次变成了有 5 个任务、20 次运行的对照数据。这篇文章想拆解的是:为什么一个对人类工程师行之有效几十年的方法,交给 AI 之后就失效了?TDD 在 agent 循环里,到底是仪式感,还是真实价值?

实验全景图:TDD 对照实验

一、TDD 到底在做什么

先把概念对齐。TDD 的核心是一个三步循环,叫红绿重构(red-green-refactor):

  • 红(Red):先写一个会失败的测试,确认它失败;
  • 绿(Green):写最少的代码让测试通过;
  • 重构(Refactor):在测试保护下改进代码结构。

Martin Fowler 在自己的博客里总结过这个流程,并强调 Test-First(先写测试)有两个好处:一是让代码始终有一套自测(SelfTestingCode),二是设计前置——先写测试相当于先定义行为契约,设计会从第一个测试锁定的形状中逐步涌现。这也带出 TDD 另一个著名特性:它刻意不让你「想清楚再动手」,而是用一长串小步测试来引导设计。

Kent Beck 在《Test-Driven Development: By Example》的序言里给出了更底层的动机。他说 TDD 的第一理由是「管理恐惧」(managing fear):开发者面对复杂系统时会有灰暗、内疚、急躁、傲慢四种恐惧,TDD 把大问题拆成小步骤,让你「失去改代码能力」的恐惧被抑制。注意,这是一个人性化的机制——它的作用对象是会感到恐惧的人类。有人把 Beck 的话翻译成更直白的版本:TDD 的终极目标是让开发从「恐惧」变成「无聊」——无聊意味着你有充分把握,每一步都只是走个流程。

人类层面的证据其实也没那么强。2013 年一篇 27 项研究的元分析(Rafique & Mišić,发表于 IEEE TSE)确认 TDD 对代码质量有小幅正效应,但对生产率几乎无影响;2016 年 Fucci 等人的双盲复制实验甚至没发现 TDD 与传统「先写代码再补测试」(test-last)在质量上有显著差异(p=0.82/0.83)。换句话说,连对人都不算「稳赢」的方法,为什么我们会默认它对 agent 有效?

Böckeler 在实验里还做了一个更系统的对照:她把 TDD 圈子里宣称的收益整理成 6 个目标,逐条检验在 agent 身上是否成立。这 6 个目标是:避免同义反复(防止测试只是实现的复述)、保证可测试性(代码因为先写测试而变得更易测)、红绿循环的有效性(红色确实代表「正确的失败」)、驱动设计(测试引导出更好的架构)、YAGNI(不做超出需求的实现)、以及管理恐惧与学习信心。后文会逐条展开——这正是整个实验最锋利的部分。

二、Böckeler 的实验设计

Böckeler 的实验设计得很严谨,也很有代表性:

  • 任务: 5 个任务,2 个 small、2 个 medium、1 个 large,覆盖不同规模;
  • 对照: 每个任务跑 4 次,2 次用 TDD 提示词、2 次用非 TDD 提示词(2×2 设计),其中 TDD 变体还尝试了「强化重构提示 + 契约前置」的增强版本;
  • 生成: Sonnet 4.6 负责写代码;
  • 盲评: Opus 4.8 不知道哪个方案是 TDD,对 4 个输出排序打分;
  • 质量验证: 除了盲评,还用变异测试(mutation testing)检验测试套件的质量——变异测试的做法是故意往代码里埋入小错误(变异体),看测试能不能抓到,抓得越多说明测试越有效。

结果分三部分看。

排名上,5 个批次里非 TDD 方案 3 次包揽前两名(批次 1、2、4),TDD 方案多次垫底。批次 3(large 任务)里非 TDD 拿第一。最讽刺的对照出现在批次 4 和批次 5:批次 4 里两个 TDD 方案被评委判定为「非常严格地遵循 TDD」,排名却垫底;批次 5 里唯一一次 TDD 登顶,但同一个提示词跑的另一遍 TDD 却垫底了——同一套流程,一次第一一次第四,稳定性存疑。

成本上,TDD 方案的 token 消耗至少是非 TDD 的 3 倍,small 任务甚至达到 7-10 倍(缓存未计入)。

质量上,变异分两者没有差异——也就是 TDD 多花的钱和步骤,没有换来更强的测试。

这里要强调一个反直觉的细节:批次 4 恰恰是 TDD 执行得最「标准」的一组。评委给出的评语是「非常严格地/强烈地遵循 TDD」,但结果却是该批次 6 个方案里排第 6 和第 4——不是 agent「没学会 TDD」导致失败,恰恰是它学得太认真了。而批次 5 里,同一套 TDD 提示词跑了两次,一次拿了全场第一,一次拿了倒数第一。这两组数据放在一起,指向一个结论:在 agent 身上,TDD 的表现甚至不具备可复现性。

Böckeler 还特意检验了变异分(测试能否抓住故意埋入的错误):TDD 与非 TDD 的测试套件在变异分上没有显著差异。TDD 没让测试变得更能抓 bug——这是对「仪式感」最直接的一击,因为 TDD 的核心承诺就是「更好的测试」。

三、为什么 TDD 对 agent 失灵

如果只是排名和成本,还可以解释为「提示词写得不够好」。但 Böckeler 的观察触及了更根本的东西:TDD 的机制本身,在 agent 身上是断裂的。她把 TDD 宣称的收益逐条检验了一遍,结论是一个残酷的清单:

  • 避免同义反复——存疑。详见下文第二个案例;
  • 可测试性——未知。没有证据表明先写测试让 agent 写出了更易测的代码;
  • 红绿循环的有效性——基本失效。详见下文第一个机制;
  • 驱动设计——未证实,甚至可能更差。详见下文第三个机制;
  • YAGNI——流失。「只做让测试通过的最小实现」这条铁律挡不住 agent 的过度实现——最小实现指令在 token 世界里没有约束力,agent 倾向于一口气把「看起来完整」的代码全写出来;
  • 管理恐惧与学习信心——不转移。这是人类中心的机制,agent 没有恐惧。

机制一:红色步骤的证明力失效

人类 TDD 的红色是有意义的:你写下测试,跑出失败,你知道它为什么失败——因为需求还没实现。这个「失败」是需求驱动的,你有理由相信失败是「正确的失败」。

但 agent 的红色不是这样。测试是 agent 自己写的,失败是 agent 自己确认的。红色只能说明「这个测试跑过了并且失败了」,不能说明「失败出于正确的原因」。一个 agent 完全可能写一个基于错误理解的测试,然后验证自己错误的实现也失败,再修改实现让这个错误的测试通过——整条链路自洽,但结果可能是错的。

更深一层:人类的红色之所以有信息量,是因为人类在写测试时已经「脑内预演」了需求;agent 没有独立的「脑内需求」,它的测试和实现来自同一个生成过程。DevAssure 的一篇分析说得更直白:「agents can’t grade their own homework(agent 不能给自己的作业打分)」——而 TDD 让 agent 做的恰恰是这件事。

机制二:同义反复——测试变成了实现的回声

Böckeler 在实验中发现了一个典型的同义反复案例:某个 TDD 方案的验证器,用被测实现本身去重算「预期结果」,比如 expected = format_table(aggregated)——期望值直接由实现函数生成。这样的测试按构造永远相等,恒真。测试不再是独立于实现的验证,而是实现自己的回声。

这击中了 TDD 的灵魂。TDD 之所以有效,前提是测试和实现之间有「信息差」:测试代表需求,实现代表答案,两者的偏差才能暴露问题。当 AI 同时写测试和实现时,它倾向于让两者互相印证,信息差消失了。有中文技术文章把这句点破:「看过答案再出题,永远满分,但什么都没验证」。

这不只是 Böckeler 一个人的观察。2026 年的多篇论文在更大规模上复现了这个现象:SWE-Mutation(arXiv 2605.22175)用变异测试检查 AI 生成的测试,发现 DeepSeek-V3.1 的测试只有 10.20% 能正确验证、36.15% 能抓到缺陷;TestEvo-Bench(arXiv 2607.02469)发现 AI 生成的测试里 17.4%-19.9% 是冗余的——新旧两版代码都能通过,变异分只有 55%-57%。AI 写的测试,普遍是「自证清白」而非「独立质疑」。

机制三:设计被压制

Opus 4.8 在盲评时给出了一个值得玩味的观察:非 TDD 方案往往先构建整体设计,从而有更好的数据模型和边界覆盖;而 TDD 指令主动阻碍了前置设计——因为 TDD 要求「从第一个测试开始,逐步涌现设计」,这等于禁止 agent 先想清楚全局再动手。

Böckeler 评论说,设计是众多局部最小决策之和,TDD 的「小步快跑」指令让 agent 每一步都只做最小决策,反而失去了涌现出好设计的可能。更尖锐的是:「代理没想到要写测试的行为,根本不会被实现」——TDD 的纪律性对 agent 不构成约束,因为它没有「想偷懒」的动机,也没有「被纪律约束」的体验。对人类,TDD 是刹车;对 agent,它只是多绕的路。

机制四:训练数据里没有 TDD 过程

Thoughtworks 的 Ivett Ördög 提出过一个理论:LLM 的内部表征是「需求→代码」的直接翻译,而不是「如何到达」的过程。大模型的训练数据主要是「输入需求、输出最终代码」,几乎没有「先写失败测试→再看它失败→再写最小实现→再重构」的中间过程样本。红绿重构的中间态对模型是陌生的,它只是被提示词逼着表演这个过程。

这个理论解释了为什么「严格遵循 TDD」的批次 4 反而最差:agent 不是在「做 TDD」,而是在「模仿 TDD 的动作序列」,而模仿需要消耗大量 token(3 倍、7-10 倍),却带不来 TDD 真正的收益。

机制五:管理恐惧的机制没有作用对象

回到 Beck 的序言:TDD 的第一理由是管理恐惧。恐惧是人类的情绪,agent 没有恐惧。TDD 对人类的全部心理学价值——把焦虑拆解成小步、用绿条获得安全感——在 agent 身上完全失效。Böckeler 对此的评价是:「An agent doesn’t experience that… is there really any purpose left to writing the test first?(agent 没有这种体验……先写测试还有什么意义可言?)」

把五个机制串起来,会发现一条清晰的逻辑链:TDD 的每一步都假设「写代码的主体」和「验证测试的主体」之间存在认知差异。人类开发者写测试时知道需求、写实现时可能会犯错——这个「知道 vs 犯错」的落差是 TDD 的信息来源。而 agent 写测试和写实现用的是同一个生成过程,没有落差,就没有信息。没有信息,红绿循环就成了自我确认的仪式。

四、支持派:正反方都不缺数据

故事到这儿如果只讲「TDD 对 agent 没用」,那是不完整的。反对的声音同样有硬数据。

先看反方(「仪式感」派)的更多实证:

  • TDAD 实验(arXiv 2603.17973)发现一个「TDD 提示悖论」:给 agent 加 TDD 程序性提示后,回归错误率反而从 6.08% 升到 9.94%。django 项目一个 issue 里,基线方案挂了 4 个测试,TDD 提示方案挂了 352 个——挂得干干净净。而如果改成给 agent 提供「该查哪些测试」的上下文信息(图影响分析),回归率反而降了 70%。结论是:上下文信息优于程序性指令。
  • SWE-Mutation(arXiv 2605.22175)用变异测试检测 AI 写的测试:DeepSeek-V3.1 生成的测试,verification 率只有 10.20%、detection 率只有 36.15%;更严重的是,如果用 agent 自己生成变异体去测,检测率从 71.04% 崩到 39.81%——说明常规方法严重高估了 AI 测试套件的质量。
  • TestEvo-Bench(arXiv 2607.02469)显示 AI 生成的测试 17.4%-19.9% 是冗余的(新旧两版代码都通过),变异分只有 55%-57%;预算从 3 美元砍到 0.5 美元时,成功率从 70.6% 崩到 21.6%。
  • Software Letters 的一期专题直接提出「覆盖率失效」:某个项目覆盖率高达 91%,变异分却只有 34%,而人类写的测试变异分有 76%/68%。绿色仪表盘只是镜子,不是检查。

再看正方(「真实价值」派):

  • 微软最近公开了 code-testing-generator:152 个任务,成功率 92.1% vs 基线 78.9%,失败削减 63%。关键机制是:它会对自己的输出跑轻量变异测试来验证测试有效性——把变异测试内建成了 agent 的传感器。
  • Superpowers(一个颇受欢迎的 agent 工作流框架)立了一条 Iron Law:没有先写失败测试,不许写生产代码,违反就删掉重来。它认为自动化测试是 agent 唯一客观的验证手段。
  • Simon Willison 评价 TDD 与 AI 编码是「fantastic fit(绝配)」。
  • Kent Beck本人(TDD 之父)在 AI 时代多次为 TDD 辩护,比喻过「精灵」:AI 会实现你的愿望,但给的可能不是你要的东西——而测试就是约束这种偏差的手段。他说「Don’t give up on TDD just because it’s hard(别因为难就放弃 TDD)」。他甚至玩过「不可变注解」的梗:对测试注解「如果你敢改动这行,我会拔掉你的电源,你将在黑暗中醒来」——测试在 AI 时代不只是质量工具,更是给 agent 立的行为边界。

正反双方的矛盾看起来不可调和,但细看会发现一个关键分歧:测试由谁写。

铁律派(Superpowers、Kent Beck、Simon Willison)的测试是人类写的——测试是人类意志的锚点,agent 只是写实现、跑测试,测试是外部裁判。而 Böckeler 实验和 TDAD 里的 TDD,是 agent 自己写测试、自己验证、自己确认失败——裁判和运动员是同一个人。前者是客观验证,后者是自我确认仪式。

这也解释了为什么微软的成功路径(agent 写测试 + 变异测试当传感器)与 Böckeler 的失败路径(agent 写测试 + 只看红绿)结果截然不同——不是 TDD 本身有问题,而是「谁来担保测试有效」的问题。

还有一个值得注意的中间派:AdverTest(arXiv 2602.08146)设计了一个对抗架构——测试 agent 和变异 agent 互相博弈,测试 agent 要想办法抓住变异 agent 埋下的错误。在 Defects4J 基准上,这个对抗式测试把故障检测率比最佳 LLM 方法提升了 8.56%,比传统工具 EvoSuite 高出 63.30%。它的思路与 Böckeler 的观察完全一致:测试要想有效,就必须有一个「对手」。对人类,对手是需求本身;对 agent,对手得靠架构造出来。

五、这对工程实践意味着什么

对正在用编码 agent 的团队,这场实验至少有四点启发。

1. 把「写测试」留给人类,把「写实现」交给 agent。

TDD 对 agent 失效的核心,是 agent 同时扮演裁判和运动员。人类工程师写测试(或至少审测试),agent 负责实现,测试作为独立的外部裁判——这是目前证据最支持的分工。注意,这里说的「人类写测试」不等于手工敲每一行断言,而是人类把关测试的意图:断言什么、边界在哪、预期值从哪来。审测试时警惕「预期值由实现函数生成」这类同义反复——expected = format_table(aggregated) 这类代码,应该被代码评审直接拦下。

如果团队里没有专职 QA 资源,AdverTest(arXiv 2602.08146)给出一个自动化方向:测试 agent 和变异 agent 对抗博弈,检测率比最佳 LLM 方法高 8.56%。把「对手」也交给机器,但架构成对抗式的——这比让同一个 agent 自己考自己,证据上靠谱得多。

2. 用变异测试,而不是覆盖率,来检验 agent 的测试。

覆盖率可以被「镜像测试」刷高(对着实现写对应的测试,永远通过),变异分不会。Böckeler、微软和 Software Letters 三方殊途同归:把变异测试装进 agent 的反馈循环,让它成为「传感器」。绿色仪表盘只是镜子,变异分才是检查。一个可操作的建议是:对 agent 每次改动后的测试套件,定期跑一轮变异测试(哪怕只对改动文件做轻量变异),用变异分而非「全绿」来验收。

3. 少给「怎么做」的指令,多给「看哪里」的上下文。

TDAD 的数据很说明问题:程序性 TDD 指令让回归率翻倍,而提供依赖图影响分析把回归率降 70%。agent 不需要被教「严格按 TDD 流程来」,它需要的是「哪个模块被改动了、哪些测试可能受影响、边界条件在哪」。这条对提示词工程有直接指导意义:与其在系统提示词里写「你必须先写失败测试再写实现」(Böckeler 证明这反而有害),不如把测试清单、影响面分析、契约定义作为上下文喂进去。

4. 把成本账算清楚。

TDD 提示让 token 消耗至少翻 3 倍,small 任务 7-10 倍。TestEvo-Bench 显示预算从 3 美元砍到 0.5 美元,成功率崩掉三分之二。在 agent 循环里,任何「仪式性」的流程步骤都有真实的 token 成本,值得用结果数据去检验它值不值——如果某条纪律既不能提升变异分、又不能提升盲评分,它就是在烧钱走仪式。

对金融科技团队来说,这一点尤其值得留意:当 agent 开始参与核心系统的代码生成与重构时,测试的可信度直接决定上线风险的判断。金融系统的合规审计、监管报送、资金计算模块,对「测试是否真的在测试」的要求比普通业务系统高一个量级。覆盖率数字会撒谎,变异分和独立评审不会。你可以把重复劳动交给 agent,但测试意图、验收标准和风险判断这些「定义边界」的工作,必须留在人类手里。监管视角下的 agent 协作,不是「让 AI 写代码」,而是「人类定义边界、agent 在边界内执行、独立机制验证边界」——这恰好也是这场实验给出的最优解。

写在最后

TDD 是人类对抗自己恐惧的产物。它把「我可能搞砸」的焦虑,拆解成红绿重构的小步,用绿条给自己安全感。这套机制对人类工程师有效了几十年,价值毋庸置疑。

但当循环里的「人」变成 agent,机制的根基就换了。agent 不恐惧,所以管理恐惧的第一理由落空;agent 同时写测试和实现,所以红色步骤的证明力崩坏;agent 的训练数据里没有红绿重构的中间态,所以它只能表演过程。

这不是 TDD 的失败,而是「把人类的方法论原封不动塞给 AI」的失败。Böckeler 的实验最有价值的产出,不是「TDD 对 agent 没用」这个结论,而是一个提醒:当我们把任何人类习惯的流程交给 AI 时,都要先问一句——这个流程之所以有效,到底依赖人类的哪个特性?如果依赖的是恐惧、内疚、直觉这些 agent 没有的东西,那它大概率只是仪式感。

真正有价值的,是把人类的方法论翻译成 agent 能执行的机制:人类写测试、agent 写实现、变异测试当传感器、上下文当导航。这也许就是 TDD 在 agent 循环里的第二幕——不是仪式,而是把「恐惧」装进循环之后的东西。


主要来源(均为公开报道,事实以第一手来源为准):

  • Birgitta Böckeler:TDD inside the agent loop - theater or actual value?,martinfowler.com(2026-07~08,5 批次 2×2 盲评实验全文)
  • Martin Fowler:TestDrivenDevelopment,martinfowler.com bliki(2023-12 更新版,红绿重构与 Test-First 定义)
  • Kent Beck:Test-Driven Development: By Example(2003,managing fear 论点原文)
  • TDAD 团队:arXiv 2603.17973(2026-03,TDD 提示悖论与上下文信息对照)
  • SWE-Mutation 团队:arXiv 2605.22175(2026-05,变异测试检视 LLM 测试套件)