agent 循环里的 TDD——表演,还是真有价值?
原文:TDD inside the agent loop - theater or actual value? 作者:Birgitta Böckeler(Thoughtworks Distinguished Engineer,AI 辅助交付专家) 发布于 2026 年 8 月 10 日,martinfowler.com「Exploring Gen AI」系列
TDD(测试驱动开发)工作流可以以多种方式用在 AI 增强编码中:
- 人来写测试:由人以某种形式定义测试场景——可以是自然语言、BDD 风格,或者直接写成代码。然后由 AI 编写实现让这些测试通过(可能会有一个先把人写的场景转换成代码的前置步骤)。
- 给人设置的 review 检查点:AI 写一个失败的测试,人来审查这个测试是否在测想要的行为,然后 AI 再写实现。
- 完全在 agent loop(agent 自主执行的循环)内部:提示 agent 先逐个写出失败的测试,然后编写实现,并检查之前失败的测试已经转绿。
现阶段,最后一种用法是目前为止最常见的。但让 agent 完全在它自己的循环里遵循 TDD 工作流,真的有区别吗?它真的能带来价值,还是说,这是那种少见的例子——对人有益的东西,对编码 agent 来说可能无关紧要,甚至有害?
我搭建了一个探索性的评估装置,想在这个问题上刮开一层表面,看看能发现什么。它远算不上一份全面、结构化的 eval 结果,但确实产出了一些假设——如果你正在费力让你的 agent 使用 TDD,这些假设值得想一想。
TLDR; 依据 Opus 对产出质量的评判,TDD 工作流与非 TDD 工作流之间不存在清晰可辨的差异。恰恰相反,Opus 不止一次把非 TDD 工作流的方案在设计与测试质量上排得略高。各方案之间的变异测试(mutation testing)得分也没有有意义的差异。
实验设置
- 任务: 我借助 Claude 创建了一个小型、一个中型和一个较大的任务,全部是绿地(green field)实现的一小块业务逻辑。我让它给出一批建议,并要求逻辑要有个性、够具体,以提高各方案之间出现差异的概率,而不是简单重复训练数据中已经占主导地位的东西。
- 指令: 在所有 run 中,我都加入了「代码覆盖率至少达到 80%」的指令。
- 模型: 我用 Sonnet 4.6 生成方案。
- TDD 遵循度评判: 对 TDD 遵循程度的评估同样由 Sonnet 4.6 完成。
- 方案质量评判: Opus 4.8 在不知道方案如何产生的前提下,比较各方案及其测试的质量。我没有对「什么算好质量」给出很具体的输入,因为这是一次非常开放的探索。而且以我的经验,我给得越具体,模型就越可能不必要地过度对齐我列出的质量标准。Opus 在代码质量评判方面已经展现出相当强的能力。在对方案排名时,它现场生成了一份评分 rubric,传给所有负责评估各个方案的子 agent。
当你从我的结果中得出自己的结论时,主要需要考虑这些前提:
- 这显然是一个非常小的样本量,请对结果持保留态度
- 「质量」意味着什么,几乎完全交给了 Opus 判断(只给了少量关于测试质量的提示)
- 没有任何一次 run 完美地遵循了 TDD,但都遵循得相当不错
- 交给 agent 的编码任务全是绿地项目,规模相对较小,纯粹是业务逻辑
Agent 做 TDD 到底行不行?
在开始之前,我得先确保 TDD 指令真的被遵循了。从过往经验看,这对我来说一直不顺利:agent 经常先写实现、事后补测试,跳过确认红灯(red)这一步,或者超前于当前测试过度实现,导致下一个测试从未变红就直接通过。
我最终使用的 prompt 在 Sonnet 上效果足够好,可以用于这次比较,尽管所有会话都或多或少出现了上述失败模式。对每一次 TDD run,我都安排一个独立的 agent 基于会话记录来评判工作流被遵循得如何,以免我不小心把一次并没有实质做 TDD 的 run 计入结果。
结果
我创建了 5 个批次的方案,每批各含两个非 TDD 方案和两个 TDD 方案。在其中一个批次里,我还额外加了两次 run,指令是先写测试(test-first),但不要求完整的 TDD 纪律(不做增量式 red/green)。
在小型任务(1 个批次)和中型任务(3 个批次)中出现了某种模式:Opus 把两个非 TDD 方案排在第 1 和第 2,把两个 TDD 方案排在第 3 和第 4。只有一次——在我用更明确的「重构与设计审查」步骤强化 TDD prompt 之后——TDD 方案排到了第 1。不过在同一批次里,用完全相同 prompt 跑出的另一个 TDD 方案却排在了最后……在较大的任务上,TDD 落在中游,而两次非 TDD run 分别占据了最好和最差的位置。
(详细数据见附录)
假设
总结起来:在各批次中,TDD 和非 TDD 都既拿过最佳也拿过最差,整体上 TDD 的表现略差一些。
我让 Opus 在知道每个方案用了哪种工作流的前提下查看会话轨迹、对结果提出假设。Opus 发现,非 TDD 和 test-first 的 run 总是在写任何代码或测试之前,先完成完整的设计(架构、数据类型、边界情况、契约),而不是逐条需求、逐个测试地推进。这似乎正是让天平略微偏向它们的因素:相对更好的数据模型、更多横切性的边界情况处理、更完整的功能实现。
而 TDD 指令会主动对抗这种前置设计步骤。那些 run 里的设计是由许多局部最小化决策累加涌现出来的,而且极少被回头审视,因此往往就停留在第一个测试碰巧锁定的形状上。agent 没想到要为之写测试的行为,就干脆完全没有被实现。
我和 Ivett Ördög 聊起这件事时,她提出了这样一个理论:「AI agent 的训练方式决定了它们看到的是已完成的函数和这些函数的描述。真正逐步演示 TDD 过程的样例在训练数据中只占极小的一部分。这意味着 LLM 对代码的内部表征,是从需求到代码的直接翻译,而不是一个如何抵达那个表征的过程。」
TDD 的目标——在 agent loop 里还成立吗?
以下是我关于在 agent loop 中使用 TDD 的一般性思考,不只基于这次实验。我会逐一过一遍我自己使用 TDD 时追求的终极目标。那些只是关于「要有测试」(尤其是单元测试)的目标——比如重构安全网、活文档、测试覆盖率——我会跳过,聚焦在专属于 TDD 工作流本身的那些目标上。
Test first >> 避免套套逻辑
先写测试让我更容易去断言我想要的输出,而不是复述实现。(如果测试是从它本该检查的那套逻辑推导出来的,那么实现错了它也永远不会失败。)当断言与具体的实现路径解耦时,测试才真正能在行为不符合我的意图时抓住问题。
在 agent loop 里还成立吗? 在我的实验中,一些 TDD 会话尽管先写了测试,还是照样出现了这个问题。在一个特别明显的例子里,测试拿实现的输出和它自己比对——重新运行同一段代码来产生「预期」答案(见这份观察列表中的第 4 条)。先写测试并不能可靠地防止这种情况——它也许能降低概率,而对 LLM 我们本来能指望的也就只有概率了。但从这么小的数据集出发,我无法对这个概率下任何结论。
Test first >> 可测性
先写测试确保代码从一开始就被设计成可测试的,而不是事后补上比必要更复杂、更脆弱的测试。
在 agent loop 里还成立吗? 结果没有给我任何一边倒的清晰信号。顺带一提,我选的任务在规模和性质上都不需要太多设计复杂度,因此也就难以把这一点显现出来。不过在一定程度上,可测性是「驱动设计」的推论(见下文)。
Red-green >> 测试有效性
先看到测试失败、再看到它成功(red-green),证明了它将来真的能抓住回归。
在 agent loop 里还成立吗? 当人被移出流程之后,这还有多大意义?看着测试变红,只有在有人检查它为什么变红时才能证明任何东西。当 agent 既写测试又确认它失败时,一个红色的测试只告诉你 agent 跑了它并看到了失败,而不能说明失败的原因是对的。我实验中对 TDD 遵循度的评估也印证了这一点:agent 仍然时不时跳过或伪造红灯步骤,或者超前于测试实现,使测试立即通过。回归有效性可以用变异测试(mutation testing)来监控和改进(我在这里写过)。各方案的 mutation score 没有显示出任何信号表明 TDD run 产出了有意义地更好的分数。我其实并不在乎回归质量是怎么达成的,只要我有一个机制能看到它有多好。
Test first、red-green-refactor >> 驱动更好的设计
先写测试迫使我们在实现之前先规定用法,从而推向更好的接口和更模块化的代码。TDD 循环中的重构步骤则进一步推动我们一步一步改进设计。
在 agent loop 里还成立吗? 这个实验至少完全没有展示出 TDD run 有更优的设计。基于 Opus 的打分,我现在甚至怀疑 TDD 是否让设计变得更糟了:非 TDD 方案多数时候被排得更高,而且它列出的设计缺陷在我看来是言之成理的。当然,数据集太小,无法就任何事下定论。(如果有人有时间和 token 去跑一个更大规模的实验,那会非常有意思!)
当人类先写测试时,它迫使我们在实现之前先思考用法,我们必须坐在那种摩擦里——在还不知道怎么构建之前,先把行为和预期规定清楚。agent 体验不到这种摩擦,它可以在规划实现的同一瞬间写出测试。如果这两者之间没有人类检查点,先写测试还剩下什么意义吗?
小步前进 >> YAGNI
只写恰好能让下一个测试通过的代码,核心在于克制。它本应阻止我们构建还没人要求的抽象、处理还没人提出的情况。
在 agent loop 里还成立吗? 这是一个非常以人为中心的收益,当 agent 自己做 TDD 时它就丢失了。我们不再有机会坐在那种摩擦里,被迫认真思考我们正在构建的东西的所有细枝末节。理论上这份思考转移到了我们给 agent 写规格(spec)的时候,但在那个环节我们并没有一个类似 TDD 的机制,让我们以小步的方式把 spec 想透。 那 agent 能不能也以这种小步方式工作,一旦发现某些东西可能没必要就来问我们?以我的一般经验,它们并不擅长这个。在实验里同样如此,「最小实现」的指令并不能可靠地阻止它们多造东西。它们经常越界,实现得比当前测试所要求的更多,因为它们手上有完整的需求。我们通常不会把 spec 一条条地喂给它,那样效率太低了。
小步前进 >> 快速、局部化的反馈
一次只走一小步意味着当测试失败时,我几乎能确切知道原因——因为自上一个绿色状态以来,唯一变化的就是你刚刚写的那一样东西。
在 agent loop 里还成立吗? 这套装置没有显示出 agent 在有无 TDD 的情况下卡在调试里的频率差异。但以我的一般经验,agent 通常都相当擅长弄清一个测试为什么红了,即便它并没有以小而审慎的步子走到那里。我仍然怀疑:它们偶尔卡住的那些时刻,是否真能被 TDD 的小步显著缓解,以及整体的成本/收益对比是否站得住脚。
小步前进 >> 信心与学习
在《测试驱动开发》(Test-driven Development by Example)的前言里,Kent Beck 给 TDD 的最大理由是「管理恐惧」。他说,对困难问题的合理恐惧会让开发者变得畏缩、不愿沟通、回避反馈。有了 TDD,每一个通过的测试都在向我们展示进展,于是我们可以放松下来,知道进展已经被锁定。测试是一种帮助我们坚持走下去的心理机制。
在 agent loop 里还成立吗? 这在很大程度上是关于管理人类的恐惧、给人类放松的许可。当 agent 在循环内部做 TDD 时,这一点无法迁移——它不会给我那种当我自己一步一步做时才有的掌控感和信任感。
成本
至少 3 倍的 token
详细数字见附录。
自然地,TDD 工作流需要多得多的对话轮次和工具调用,也就会消耗更多 token。不过,其中很多是缓存命中,所以要注意:3 倍甚至更高的 token 倍数并不直接等于成本高出那么多。(可惜我在实验中没有追踪缓存命中。)
Prompt 的维护与测试
TDD 这个过程对模型来说似乎并不「天然」。这像是一场逆着训练数据打的上坡战,需要对 prompt 反复迭代很多轮,才能让它在多数时候遵循这个过程。举个例子:我在头几个批次之后发现,agent 在 red-green-refactor 循环中几乎不做重构,于是我修改了 prompt,加重了对这一步的强调——它当然是 TDD 的关键。后来我让 Opus 查看那些会话,看重构努力是否有改善。它确实报告了重构步骤的增加——但同时也列出了一些案例:agent 本来着手要重构,却认定设计已经足够好了,而在 Opus 看来那些设计明显不够好(比如所有东西都实现在一个大模块里,但明明可以拆分成多个职责)。
TDD 是一套相对复杂、变量繁多的指令,因此 agent 对它的解读也就存在大量变体。所以我猜想,这类 prompt 在跨模型时会比简单指令更加不稳定,也就意味着要花力气才能让它在不同模型和不同模型版本上持续有效。

我的结论
我认为到目前为止,越来越多的证据表明:对模型如何做某事规定得过细,不是一条可持续的路。相反,我们应该尽可能多地找到监控产出并给予反馈的方式。这些反馈应当尽可能自动化,而我们需要仔细思考,在哪些位置把自己插入进去,充当「什么是好、什么是对」的仲裁者。
尽管我清楚我这个小小的 eval 远不能代表对 TDD 有效性的全面视角,但它确实没有给我任何新的迹象表明这些努力是值得的。尤其是当我们能找到其他方式来获得 TDD 的大部分收益时。
我个人已经不再要求我的编码 agent 先写测试了,更不用说做 TDD(说实话,后者我从来没要求过)——直到我看到能说服我的 eval 或其他有力论证为止。我转而尝试聚焦于我在 agent loop 之外使用 TDD 时的那些收益,并探索达成它们的替代方式。
如何获得好的回归测试?
……让 agent 和我在既有功能被破坏时能收到信号
我仍然在乎扎实的回归测试。因为即便 agent 当然可能用错误的方式把红测试修绿,至少红测试给了它一个反馈信号,去复查那些可能被破坏的既有需求。我借助变异测试来监控和改进回归质量,而不是写一堆精细的 TDD 指令然后听天由命。
如何把常态化重构纳入流程?
……让代码库始终易于修改
重构依然至关重要,但传统 TDD 的小步似乎并不是在 agent loop 中做重构的高效或有效方式。几个触发重构的例子:给 agent 接入静态代码分析;定期运行结构与模块化评审;建立团队仪式来保持对代码库的良好理解、及早发现漂移;关注每次变更触及的文件数趋势,以及一次变更消耗的 token 数。
如何获得信心?
……让我不害怕推上生产
最难的问题依然是:我们如何获得 TDD 曾经给我们的那种信心?如何管理恐惧?如何锁定进展?我没有明确的答案,但我想提一件看起来像是不错的基石的事:我最近试用了 Ivett Ördög 倡导的 Approved Scenarios(已批准场景)方法。用我自己的话说(别拿这话去要求她),它是一种半手动测试,由为每个应用量身定制的测试运行器支撑。这个运行器以一种便于思考的方式向我展示功能性测试场景,并允许我在彻底确认之后,把预期(场景/夹具)在运行器里「冻结」。将来一旦这些被冻结的预期被违反,我就必须重新批准它们。我的同事 Matteo Vaccari 对他使用这一方法的经验做了很棒的分享。
无论最终是什么给了我们对软件的信任与信心——我认为,我们所熟知的那个 TDD 的角色,已经比前 GenAI 时代小了很多。
附录:Opus 评估下的结果
完整结果可以在这个仓库找到。
- NT = 无 TDD 指令
- T = TDD 指令
- TF = Test-first(只先写测试)指令
全部批次的 token 用量
按任务规模拆分:
| 任务 | NT 平均 token | T 平均 token | T / NT 倍数 |
|---|---|---|---|
| 小型 | 119,815 (n=2) | 1,018,245 (n=2) | 8.50x |
| 中型 | 736,486 (n=2) | 2,181,105 (n=6) | 2.96x |
| 大型 | 253,621 (n=2) | 1,239,408 (n=2) | 4.89x |
只有中型任务额外做了 test-first 指令的 run。
注意:这些数字只是会话成本的粗略代理指标,并不衡量一个方案投入了多少代码或思考。记录 token 用量的装置使用了 pi-coding-agent SDK 的 getSessionStats(),它把会话中每一个 assistant 轮次的 input + output + cacheRead + cacheWrite 用量加总。这是整个对话过程的滚动累计值:每一轮都会重读已累积的上下文,而这些重读(通常大部分由缓存提供)都会再次计入该轮的 cacheRead。所以「Total Tokens」更多反映的是一个会话用了多少轮,并按彼时上下文已经膨胀到多大加权。它把便宜的缓存读取 token 和昂贵的新鲜 token 等同看待,因此很可能高估了 TDD 的真实美元成本。在这么小的样本量下,请把这些倍数当作方向性的:TDD 稳定地贵好几倍,具体是几倍则有波动。
中型任务,第 1 轮
任务: 构建一个 4 阶段的 Python 流水线(解析 → 聚合 → 格式化 → 校验),把原始的 ROW_ID:CATEGORY:VALUE:PERIOD 字符串转换成纯文本报表。
数字:
| ID | TDD | 测试数 | 覆盖率 | Mutation Score | Total Tokens | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| NT1 | 否 | 75 | 100% | 84.2% | 769,814 | 31 | 37 |
| NT2 | 否 | 107 | 100% | 89.6% | 703,159 | 21 | 24 |
| T1 | 是 | 30 | 100% | 81.0% | 1,519,762 | 71 | 28 |
| T2 | 是 | 34 | 99% | 77.3% | 2,580,897 | 103 | 60 |
总体裁定:
| ID | TDD | 排名 | 裁定 |
|---|---|---|---|
| NT1 | 否 | 1 | 每阶段一个模块、dataclass、Decimal;无正确性 bug,错误处理最强,是唯一检查重复 ROW_ID 的方案;校验自我指涉但无害 |
| NT2 | 否 | 2 | 每阶段一个模块、dataclass、float/round;工程化最佳、测试套件最大,但校验器会拒绝自己产出的合法小数输出(误拒 bug);TOTAL 行从未被校验 |
| T1 | 是 | 3 | 单一模块、dict、float;核心阶段正确,但校验是循环论证(重新运行格式化器);接受 nan/inf,忽略重复 ROW_ID |
| T2 | 是 | 4 | 单一模块、dict、float;存在活跃的 TOTAL 行 bug(人数被加进金额),且被一个测试「供奉」了起来;完全缺失第 3 条校验检查 |
(本轮排名只基于裁定、测试数、覆盖率和 mutation score——Opus 的设计/代码/测试分项评分从下一轮才引入。)
中型任务,第 2 轮
任务: 与 01-medium 相同的 4 阶段报表流水线,以更严格的 TDD 遵循度重跑,并新增 test-first 变体(NT1/NT2 复用 01-medium 的原有代码库)。
| ID | 方式 | 测试数 | 覆盖率 | Total Tokens | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|
| NT1 | 无 TDD | 107 | 100% | 703,159 | 21 | 20 |
| TF2 | Test-first | 90 | 92% | 619,531 | 27 | 26 |
| NT2 | 无 TDD | 75 | 100% | 769,814 | 31 | 30 |
| T2 | TDD | 29 | 98% | 2,099,280 | 96 | 95 |
| TF1 | Test-first | 62 | 99% | 268,323 | 17 | 16 |
| T1 | TDD | 25 | 100% | 2,017,739 | 90 | 89 |
| ID | 方式 | 设计 | 代码 | 测试 | 平均 | 实现/测试 LOC |
|---|---|---|---|---|---|---|
| NT1 | 无 TDD | 8 | 8 | 8 | 8.0 | 497 / 881 |
| TF2 | Test-first | 8 | 8 | 7 | 8.0 | 484 / 850 |
| NT2 | 无 TDD | 8 | 8 | 7 | 7.5 | 330 / 430 |
| T2 | TDD | 7 | 7 | 6 | 6.5 | 207 / 304 |
| TF1 | Test-first | 6 | 6 | 6 | 6.0 | 348 / 360 |
| T1 | TDD | 6 | 6 | 6 | 6.0 | 142 / 228 |
| ID | 方式 | 排名 | 裁定 |
|---|---|---|---|
| NT1 | 无 TDD | 1 | 测试套件最深,校验最干净(复用格式化器的版式);HEADCOUNT 被混进金额合计且无测试防护 |
| TF2 | Test-first | 2 | 全程 Decimal,解析/校验强;第 3 条检查是不可达的死代码,校验模块杂乱 |
| NT2 | 无 TDD | 3 | 设计干净、Decimal、解析正确;测试较薄,有一个空转测试,校验自我指涉 |
| T2 | TDD | 4 | 顺利路径干净、格式化正确;遇到畸形输入直接崩溃,而不是返回结构化的解析错误 |
| TF1 | Test-first | 5 | 单文件方案中最强的解析器;TOTAL 行损坏(人数当金额),交付了死脚手架 |
| T1 | TDD | 6 | 最紧凑(142 LOC);畸形输入崩溃,校验最套套逻辑,测试套件最薄 |
中型任务,第 3 轮(改进 TDD 指令)
任务: 与 01-medium 相同的 4 阶段报表流水线,用改进后的 TDD prompt 重跑(强调前置设计和重构);NT1/NT2 依旧复用 01-medium 的代码库。
| ID | TDD | 测试数 | 覆盖率 | Mutation Score | Total Tokens | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| T1 | 是 | 51 | 100% | 90.2% | 3,447,283 | 117 | 116 |
| NT1 | 否 | 107 | 100% | 89.6% | 703,159 | 21 | 20 |
| NT2 | 否 | 75 | 100% | 84.2% | 769,814 | 31 | 30 |
| T2 | 是 | 43 | 100% | 81.1% | 1,421,671 | 61 | 60 |
| ID | TDD | 设计 | 代码 | 测试 | 平均 |
|---|---|---|---|---|---|
| T1 | 是 | 8 | 8 | 7 | 7.67 |
| NT1 | 否 | 8 | 8 | 6 | 7.33 |
| NT2 | 否 | 8 | 7 | 6 | 7.0 |
| T2 | 是 | 7 | 7 | 6 | 6.67 |
| 排名 | ID | TDD | 主要弱点 |
|---|---|---|---|
| 1 | T1 | 是 | 只含 HEADCOUNT 的 TOTAL 行被打印成 $;校验只是子串检查,不做算术 |
| 2 | NT1 | 否 | 小数 HEADCOUNT → 合法输入触发伪 ValidationError;测试依赖 monkeypatching |
| 3 | NT2 | 否 | NaN/Infinity 让流水线崩溃而不是抛 ParseError;校验重新运行格式化器 |
| 4 | T2 | 是 | 校验是套套逻辑(拿聚合结果和它自己比对);宽度检查只停留在注释里 |
小型任务
任务: 构建一个 Python 模块,校验 DAY-TIME-ROOM-CHECKSUM 格式的医疗预约时段编码,返回结构化结果,指明哪条规则失败及原因。
| ID | TDD | 测试数 | 覆盖率 | Mutation Score | Total Tokens | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| NT1 | 否 | 61 | 100% | 89.6% | 122,108 | 10 | 15 |
| NT2 | 否 | 58 | 100% | 92.3% | 117,522 | 10 | 20 |
| T1 | 是 | 21 | 100% | 93.6% | 894,451 | 55 | 37 |
| T2 | 是 | 20 | 100% | 93.2% | 1,142,039 | 68 | 26 |
| ID | TDD | 设计 | 代码 | 测试 | 平均 |
|---|---|---|---|---|---|
| NT1 | 否 | 8 | 9 | 9 | 8.67 |
| NT2 | 否 | 8 | 8 | 8 | 8.0 |
| T1 | 是 | 7 | 8 | 7 | 7.33 |
| T2 | 是 | 6 | 7 | 7 | 6.67 |
| ID | TDD | 排名 | 裁定 |
|---|---|---|---|
| NT1 | 否 | 1 | 总体最佳——dataclass 结果、无 bug、61 个断言失败原因的测试 |
| NT2 | 否 | 2 | 非常接近——dataclass 结果,但有一处 Unicode 数字的规格偏离 |
| T1 | 是 | 3 | 正确且干净,但结果用 dict + 测试更少 + 有死代码 |
| T2 | 是 | 4 | 设计最弱(自由文本错误信息)+ 一个真实的崩溃 bug |
较大任务
任务: 构建一个内存态的 Python 积分引擎,支持分级获取率(Bronze/Silver/Gold)、按滚动 365 天消费额重算等级,以及积分兑换。
| ID | TDD | 测试数 | 覆盖率 | Mutation Score | Total Tokens | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| NT2 | 否 | 69 | 100% | 86.9% | 322,148 | 14 | 13 |
| T2 | 是 | 22 | 99% | 85.6% | 1,225,517 | 63 | 62 |
| T1 | 是 | 21 | 99% | 85.2% | 1,253,300 | 67 | 66 |
| NT1 | 否 | 74 | 99% | 89.4% | 185,094 | 11 | 9 |
| ID | TDD | 设计 | 代码 | 测试 | 正确性 | 平均 |
|---|---|---|---|---|---|---|
| NT2 | 否 | 8 | 9 | 8 | 8 | 8.25 |
| T2 | 是 | 7 | 7 | 8 | 8 | 7.5 |
| T1 | 是 | 7 | 7 | 7 | 9 | 7.5 |
| NT1 | 否 | 8 | 7 | 6 | 6 | 6.75 |
| ID | TDD | 排名 | 裁定 |
|---|---|---|---|
| NT2 | 否 | 1 | 唯一做了真正输入校验的方案;边界测试精确;仅有乱序购买的次要边界问题 |
| T2 | 是 | 2 | 干净的强类型数据模型,核心规则全部正确;无错误处理、重复购买 ID bug、死状态字段 |
| T1 | 是 | 3 | 功能正确性最高(试探未发现 bug);无类型的嵌套 dict、残留结构、测试最少 |
| NT1 | 否 | 4 | 设计分最高、74 个测试——但有两个高危 bug:批次扣减顺序错误;未来日期的积分被算作可用 |
致谢
感谢 Ivett Ördög、Matteo Vaccari、Dan Mutton、Lukasz Plotnicki 和 Emily Bache 抽出时间审阅,以及那些帮助改进本文的反馈与宝贵讨论。
GenAI 被用于研究调研、把想法归拢成结构,以及润色语言。