FunTester 用结果反馈替代 AI 的强制 TDD

FunTester · 2026年08月18日 · 553 次阅读

让编程智能体严格执行 TDD,未必能写出更好的代码。在这组探索性评测中,TDD 没有带来更好的设计或更高的变异测试得分,Token 用量却可能增加到数倍。

如果你正在为智能体设计测试流程,读完这篇文章可以带走两件事:先判断哪些 TDD 收益来自人的思考,而不是流程本身;再用变异测试、可靠的回归测试和重构信号来约束结果,而不是机械要求智能体走红绿循环。

测试驱动开发(TDD)用于 AI 辅助编程,常见有三种层次:

  1. 人来写测试:人先用自然语言、BDD 风格或代码定义测试场景,再让 AI 编写实现,让测试通过。AI 也可以先把这些场景转换成可执行测试。
  2. 人来把关:AI 先写一个失败测试,人先判断这个测试是否测对了,再让 AI 编写实现。
  3. 完全交给智能体:要求智能体一次只写一个失败测试,随后编写实现,并确认这条测试已经通过。

目前,第三种用法远比前两种常见。问题是:这套对人有效的做法,放到编程智能体身上是否仍有价值,甚至会不会适得其反?

我做了一组探索性评测,想初步回答这个问题。它还谈不上全面、结构化,但足以帮助我们重新审视:给智能体规定步骤,究竟比给它建立结果反馈更重要,还是更不重要?

评测设置

  • 任务:我借助 Claude 设计了小、中、大 3 个任务,都是从零实现一小段业务逻辑。我让它给出多种设想,并要求逻辑足够具体、略带非典型性,尽量让不同方案产生差异,而不是重复训练数据中常见的模式。
  • 指令:所有运行都要求达到至少 80% 的代码覆盖率。
  • 模型:我使用 Sonnet 4.6 生成方案。
  • TDD 遵循度判断:同样由 Sonnet 4.6 评估是否遵循 TDD。
  • 方案质量判断:Opus 4.8 在不知道方案生成过程的前提下,比较实现和测试质量。这次探索比较开放,所以我没有把质量标准列得很细;按我的经验,标准越细,模型越容易过度迎合这些标准。Opus 已表现出较强的代码质量判断能力。比较时,它会临时生成一套量规,再交给评估单个方案的子智能体。

解读结果时,需要注意以下限制:

  • 样本量显然很小,只能谨慎参考。
  • 质量的定义几乎完全交给 Opus,仅对测试质量给了少量提示。
  • 没有任何一次运行完美遵循 TDD,但总体遵循得还可以。
  • 交给智能体的都是从零开始、规模相对较小、只涉及业务逻辑的任务。

智能体有多擅长 TDD

在开始比较前,我需要先确认 TDD 指令确实被遵循。以往的经验并不理想:智能体常常先写实现、再补测试,跳过确认测试变红的步骤,或者提前实现得过多,以至于下一条测试从未真正变红就直接通过了。

最后定下的提示词,已经能让 Sonnet 基本遵循要求,足以用于比较。不过,每个会话都在不同程度上出现过上述问题。对于每次 TDD 运行,我都让独立智能体根据会话记录判断执行情况,避免把没有真正按 TDD 进行的运行纳入比较。

结果

我一共跑了 5 批方案,每批包含 2 个非 TDD 方案和 2 个 TDD 方案。其中一批还额外加入了 2 次先写测试的运行,但不要求完整 TDD 流程,也就是不要求增量式红绿循环。

小型任务有 1 批,中型任务有 3 批,出现了一个倾向:Opus 把两个非 TDD 方案排在第 1 和第 2,把两个 TDD 方案排在第 3 和第 4。只有一次,我在 TDD 提示词中更明确地加入重构和设计评审步骤后,一个 TDD 方案排到了第 1;但同一批中,使用相同提示词的另一个 TDD 方案却排在最后。对于较大任务,TDD 处于中间位置,两个非 TDD 方案则分别拿到了最好和最差的位置。

假设

总的来说,TDD 和非 TDD 都曾产出最好或最差的方案,但 TDD 的整体表现略差。

知道每个方案采用的工作流后,我让 Opus 回看会话记录并推测原因。它发现,非 TDD 和先写测试的运行,在写代码或测试之前,都会先把整体设计想清楚,包括架构、数据类型、边界情况和契约,而不是跟着一条需求或一条测试往前走。这似乎让它们的数据模型更合理,考虑到的边界情况更多,功能也更完整。

TDD 指令恰恰不利于这种前置设计。TDD 运行中的设计,是从一个个只解决眼前问题的决定里慢慢长出来的,而且很少回头调整,所以常常会被第一条测试限制住。智能体没有想到写测试覆盖的行为,往往就根本没有实现。

我和 Ivett Ördög 讨论时,她提出了一个解释:AI 智能体的训练数据主要是完成后的函数及其说明,真正一步一步展示 TDD 的样例极少。因此,大语言模型更擅长把需求直接映射成代码,而不是复现一步步推导出代码的过程。

智能体循环中的 TDD

下面是我对智能体循环中使用 TDD 的一些总体思考,不只来自这次实验。我会逐一讨论自己使用 TDD 时真正想得到什么,先跳过测试本身带来的收益,例如重构安全网、活文档和测试覆盖率,只看 TDD 这种工作方式特有的价值。

先写测试:避免同义反复

先写测试能让我们先说清希望得到什么,而不是把实现换一种说法再写一遍。如果测试的期望值也来自它本该检查的那套实现逻辑,实现出错时它很可能不会失败。只有断言不依赖具体实现路径,测试才能真正发现行为是否偏离预期。

放到智能体循环里还成立吗?

在我的实验中,即使先写了测试,部分 TDD 会话仍然出现了这个问题。有一个特别明显的例子:测试先用实现本身重新计算预期答案,再拿实现输出和它比较。先写测试并不能可靠地避免这类问题,它也许会降低发生概率;但样本太小,我无法判断能降低多少。

先写测试:可测试性

先写测试能让代码从一开始就更容易测试,而不是事后补上复杂又脆弱的测试。

放到智能体循环里还成立吗?

这次结果看不出明显差别。任务规模和性质都比较简单,设计复杂度不足以把这个差异充分暴露出来。而且,可测试性本身也和测试驱动设计有关。

红绿循环:测试有效性

先看到测试失败,再看到它通过,也就是红绿循环,能证明这条测试确实能抓住一次回归问题。

放到智能体循环里还成立吗?

人不参与时,这一步还有多大意义?只有有人检查测试为什么变红,红色测试才真正能说明问题。智能体既写测试又确认它失败时,只能说明它执行过测试并看到了失败,不能说明失败原因是对的。对 TDD 执行情况的评估也印证了这一点:智能体仍会跳过或伪造变红步骤,或者提前实现,让测试一开始就通过。我们可以用变异测试持续检验回归测试的质量。各方案的变异得分没有显示 TDD 明显优于非 TDD。我并不在意回归质量具体通过什么方式获得,只要有机制能衡量它。

先测红绿重构:设计收益

先写测试会迫使我们在实现前想清楚怎么用,从而推动更好的接口和更模块化的代码。TDD 循环中的重构步骤,也会促使我们逐步改善设计。

放到智能体循环里还成立吗?

这次实验至少没有显示 TDD 方案的设计更好。根据 Opus 的评分,我甚至开始怀疑 TDD 会不会让设计更差:非 TDD 方案更常排在前面,它列出的设计缺陷也确实有道理。当然,数据集太小,不能据此下定论。如果有人有时间和 Token 跑更大规模的实验,会很有价值。

人先写测试时,必须在还没想清楚实现方案前,认真思考怎么用、要有什么行为、结果该是什么。智能体不会经历这种阻力,它可以在规划实现的同时写出测试。如果两者之间没有人的检查点,先写测试还剩下什么意义?

小步前进:YAGNI

只写刚好够通过下一条测试的代码,本质上是一种克制。它本应阻止我们过早搭抽象,或处理还没人提出的情况。

放到智能体循环里还成立吗?

这项收益高度依赖人。当智能体独自执行 TDD 时,它就消失了。我们不再经历那种必须认真想清楚自己在构建什么的阻力。理论上,这种思考被前移到了为智能体写规格时,但我们没有类似 TDD 的机制,能一步步推敲规格。

智能体能否也按小步推进,发现可能没必要的内容时就来问我们?按我的经验,它们并不擅长这么做。实验中也是如此:即使要求最小实现,它们也常常做得过多。因为智能体拿到的是完整需求,它很容易超出当前测试的范围。我们通常也不会把规格一条条喂给它,那样效率太低。

小步:局部反馈

一次只走一小步,意味着测试失败时,我们几乎知道原因:从上一次测试通过开始,唯一变化的就是刚写下的那一项。

放到智能体循环里还成立吗?

这次设计没有证明使用或不使用 TDD 时,智能体是否更容易卡在调试里。但按我的经验,即使不刻意控制小步,智能体通常也能判断测试为什么变红。我仍然怀疑,TDD 的小步能否明显减少它们偶尔卡住的情况,以及整体上是否划算。

小步:信心和学习

Kent Beck 在《测试驱动开发:示例引导》前言中,把管理恐惧视为 TDD 最重要的理由。他认为,对困难问题的合理恐惧会让开发者犹豫、不愿沟通,也会回避反馈。每一次测试通过都让我们看到进展,因此可以放松下来,知道进展已经稳住了。测试是一种帮助我们继续前进的心理机制。

放到智能体循环里还成立吗?

这完全是在帮人管理恐惧,也让人有理由放松。当智能体在循环内执行 TDD 时,它并不会把同样的控制感和信任感带给我。

成本

至少 3 倍 Token

TDD 本来就要经历更多轮交互和工具调用,因此会消耗更多 Token。不过,其中许多可能命中缓存,所以3 倍或更高的 Token 倍数并不等于成本就增加了这么多。我没有在实验中记录缓存命中情况。

提示词维护

TDD 并不是模型默认会遵循的过程。想让它大多数时候按流程走,需要反复调整提示词。例如,前几批实验后我发现智能体在红绿重构循环里几乎没有重构,于是修改提示词,更强调这个对 TDD 很关键的步骤。后来我让 Opus 回看这些会话,判断重构是否真的改善。它发现重构步骤有所增加,但也列出了一些情况:智能体打算重构,却在 Opus 明显认为设计仍需改进时,判断当前设计已经足够好。比如,所有内容仍放在一个大模块中,其实可以拆成多个职责。

TDD 是一组比较复杂、变量很多的指令,智能体会用许多不同方式理解它。我推测,这类提示词在不同模型和版本间的波动,会比简单指令更大;想让它持续生效,也需要持续维护。

我的结论

我认为,把模型做事的步骤规定得过细,并不是长久之计。更值得做的是尽可能监控结果,并持续提供反馈。这些反馈应尽量自动化;同时也要想清楚,哪些地方需要人来判断对错和质量。

这次小评测远不足以代表 TDD 效果的全貌,但它没有给我新的证据,证明继续投入这些努力值得,尤其是在我们能用其他办法获得 TDD 大部分收益的情况下。

在看到更有说服力的评测或论据前,我个人不会再要求编程智能体先写测试,更不会要求它做 TDD。说实话,我以前也没有这么要求过。我会把注意力放回自己在智能体循环之外使用 TDD 时得到的收益,并继续寻找其他实现这些收益的办法。

可靠回归测试

无论是智能体还是人,都需要在已有功能被破坏时尽快得到信号,因此仍然需要可靠的回归测试。智能体当然可能用错误方式修复失败测试,但失败测试至少会提醒它重新核对可能被破坏的既有需求。我会借助变异测试监控和提升回归质量,而不是给出复杂的 TDD 指令后期待一切顺利。

定期重构

想让代码库保持易改,重构依然很重要。但传统 TDD 的小步在智能体循环中似乎既不高效,也不够有效。可以把以下信号作为重构触发点:让智能体接入静态代码分析;定期评审代码结构和模块化;建立团队机制,持续理解代码库并尽早发现设计漂移;跟踪每次变更触及的文件数量,以及完成一次变更要消耗的 Token 数量趋势。

建立信心

把变更推向生产环境时,最难的还是怎么建立信心:如何获得 TDD 曾经带来的安全感,如何管理恐惧,如何确认进展没有丢?我没有明确答案,只想提一个可能有用的做法:我最近尝试了 Ivett Ördög 倡导的 Approved Scenarios。按我的理解,它是一种半手工测试方式,每个应用都配有定制的测试运行器。运行器会用更容易理解的方式展示功能测试场景;在我认真确认后,可以把预期冻结下来,也就是固定场景和测试数据。以后只要这些预期不再满足,我就必须再次审批。我的同事 Matteo Vaccari 曾分享过他使用这种方法的经验。

无论未来我们靠什么信任软件、建立信心,我都认为,GenAI 出现前那种 TDD 的作用已经明显缩小。


收录于 FunTester 原创专题:AI ,测试有点东西

相关阅读:Agentic Coding 的监督机制 · AI 写的快,回归测试要跟上 · 给测试看的 Agentic SDLC · AI 写用例之后,测试稀缺能力是什么 · TDD 测试驱动开发的基础

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暫無回覆。
需要 登录 後方可回應,如果你還沒有帳號按這裡 注册