在 Agent 循环中运用测试驱动开发(TDD)的实验
TDD(测试驱动开发)工作流可以通过多种方式与 AI 辅助编码结合使用:
- 人类编写测试:人类以某种形式定义测试场景——自然语言、BDD 风格,或者直接写成代码。随后 AI 编写实现代码让这些测试通过(第一步或许会先把人类的场景转换成代码)。
- 人类审查检查点:AI 编写失败的测试,人类审阅该测试,确认它验证的正是期望的行为,然后 AI 再编写实现代码。
- 完全在 Agent 循环内:让 Agent 先逐个写出失败的测试,再编写实现代码,并检查之前失败的测试是否变绿。
目前来说,最后一种用法最为常见。但要求 Agent 完全在它自己的循环内遵循 TDD 工作流,真的会有差别吗?它真的能带来价值,还是说,这是少数几个对人类有益、对编码 Agent 却可能无关甚至有害的例子之一?
我搭建了一个探索性评估方案来初步探究这个问题,看看会发现什么。这远非全面、结构化的评估结论,但它确实催生了一些值得深思的假设,尤其适合那些正努力让 Agent 用上 TDD 的人。
TLDR: 根据 Opus 对结果质量的评判,采用或不采用 TDD 工作流,结果没有明显可辨的差异。相反,Opus 不止一次把非 TDD 工作流的方案在设计和测试质量上评为略高。各方案之间的变异测试得分同样没有显著差别。
设置
- 任务: 我借助 Claude 创建了小型、中型和大型三个任务,都是从零实现的业务逻辑。我让它给出了一连串建议,要求包含独特而具体的逻辑,以提高不同解决方案之间出现差异的可能性,而不是让它们重复训练数据里已有的主流做法。
- 指令: 每次运行,我都要求至少达到 80% 的代码覆盖率。
- 模型: 我使用 Sonnet 4.6 生成解决方案。
- TDD 遵循度评判: 对 TDD 遵循度的评估也由 Sonnet 4.6 完成。
- 解决方案评判: Opus 4.8 在不知道方案创建方式的情况下,比较了各解决方案及其测试的质量。我并没有就"什么算高质量"给出非常具体的输入,因为这是一次相当开放的探索。根据我的经验,我给出的标准越具体,模型就越可能过度拘泥于我列出的那些质量标准。Opus 已被证明是代码质量评判方面相当有能力的模型。在给方案排名时,它临场创建了一套评分标准,并下发给评估各方案的所有子 Agent。
如果你要从我的结果中得出自己的结论,需要注意的主要事项如下:
- 这显然只是非常小的样本,请谨慎参考。
- 对"质量"含义的评判几乎完全留给了 Opus(只对测试质量做了一些提示)。
- 没有任何一次运行是完美遵循 TDD 的,不过总体遵循得还不错。
- 交给 Agent 的任务都是从零开始、相对较小的编码任务,内容纯粹是业务逻辑。
Agent 在 TDD 方面到底有多好?
在开始之前,我需要确保 TDD 指令真的得到了遵循。从以往的经验看,我在这件事上一直不太顺利:Agent 经常先写实现代码、再生成测试,跳过确认"变红"这一步,或者在当前测试之前就过度实现,导致下一个测试没有经历过红色就直接通过了。
我最终使用的提示词与 Sonnet 配合得足够好,可以用于这场比较;不过所有会话都或多或少表现出了一些这类问题。每次 TDD 运行,我都会让一个独立的 Agent 根据会话记录评判工作流被遵循的程度,这样我就不至于无意间把那些并没有真正执行 TDD 的运行也算进去。
结果
我生成了 5 批解决方案,每批包含两个非 TDD 方案和两个 TDD 方案。其中一个批次,我还额外加了两次"只先写测试"的运行,不要求完整的 TDD 纪律(没有增量式的红/绿)。
在小型(1 批)和中型(3 批)任务中,呈现出一种规律:Opus 把两个非 TDD 方案排在第 1、2 位,而两个 TDD 方案排在第 3、4 位。只有一次——在我强化 TDD 提示词、加入更明确的重构和设计审查步骤之后——某个 TDD 方案才拿到了第 1 名。不过在同一批次中,另一个用完全相同提示词跑的 TDD 方案却排在了最后……至于更大的那个任务,TDD 方案居中,而两个非 TDD 方案分别拿到了最好和最差的名次。
(详情见附录)
假设
总体来看,在各个批次中,TDD 和非 TDD 方案都既拿过最佳、也拿过最差,而 TDD 的整体表现略逊一筹。
在得知各方案对应哪种工作流之后,Opus 被要求查看会话轨迹、推测结果成因。它发现,非 TDD 和测试先行的运行,总是先在写任何代码或测试之前就建立完整设计(架构、数据类型、边界情况、契约),而不是跟着需求/测试一样一样地逐步推进。这似乎正是让天平略微偏向更好数据模型、更全面边界情况、更完整功能的关键因素。
TDD 指令实际上在妨碍这种前置设计。在这些运行里,设计由大量局部最小决策堆叠而成,很少被回头审视,因此最终形态往往取决于第一个测试恰好锁定了什么。Agent 没想到要写测试的那些行为,最终也完全没有被实现。
后来我和 Ivett Ördög 聊起这个,她提出了一个理论:"AI Agent 是这样被训练出来的:它们见过大量完整函数以及对这些函数的描述。而真正的逐步 TDD 示例,在训练数据里只占极小一部分。这意味着 LLM 对代码的内部表征,是'从需求直接映射到代码',而不是'如何一步步得到那个表征'的过程。"
TDD 的目标:在 Agent 循环中仍然能实现吗?
下面是我对"在 Agent 循环中使用 TDD"的一些一般性思考,而不仅仅基于这次实验。我会逐个讨论我个人使用 TDD 时最根本的目标,跳过那些"首先得有测试"层面的目标(尤其是单元测试带来的,比如重构安全网、活文档、代码覆盖率),只聚焦于 TDD 工作流特有的一些目标。
测试先行:避免同义反复
测试先行,让我更容易去断言我真正想要的输出,而不是复述实现逻辑本身。这种测试在实现出错时永远不会失败——因为它正是从理应被检查的那套逻辑中推导出来的。当断言与具体实现路径解耦后,测试才能真正捕捉到"行为不符合我预期"的情况。
在 Agent 循环中仍然能实现吗? 在我的实验里,有些 TDD 会话尽管先写了测试,还是踩中了这个问题。有一个特别明显的例子:测试拿实现的输出跟它自身比较——重新运行同一份代码来生成"预期"答案(见这份观察列表中的第 4 项)。先写测试并不能可靠地避免这种情况——它或许能降低发生的概率,而这已经是我们对 LLM 能指望的全部了;但单凭这么小的数据集,我无法对那个概率得出任何结论。
测试先行:可测试性
测试先行能确保代码从设计之初就是可测试的,而不是事后补测试,结果补出来的测试比必要情况更复杂、更脆弱。
在 Agent 循环中仍然能实现吗? 结果没有给出任何清晰明确的信号。况且,我选的任务在规模和性质上,并不需要太多设计复杂度,因此也很难让这一点浮出水面。不过在某种程度上,可测试性其实是"以测试驱动设计"的自然推论(见下文)。
红-绿:测试有效性
先看到测试失败、再看到它通过(红-绿),才能证明它确实能抓住回归。
在 Agent 循环中仍然能实现吗? 当人类从循环中被移除后,这一点究竟还有多大意义?只有当有人去核查它为什么变红时,"看到测试变红"才具有证明力。当 Agent 自己写测试、自己确认失败时,测试变红只能告诉你"Agent 跑了测试并且看到了失败",却无法告诉你"失败的原因是对的"。我在实验中对 TDD 遵循度的评估也印证了这点:Agent 有时仍会跳过或伪造红色步骤,或者提前实现功能,让测试直接通过。回归测试的有效性,可以用变异测试来监控和改进(我在这篇文章里写过)。各方案的变异测试得分也没有任何迹象表明,TDD 运行产出的变异测试成绩会明显好于非 TDD 运行。我并不真的在乎回归质量是怎么做到的,只要我有一套机制能看出它到底好不好。
测试先行,红-绿-重构:驱动更好的设计
先写测试,迫使我们在动手实现之前先明确用法,从而促成更好的接口和更模块化的代码。TDD 循环里的重构步骤,又进一步推动我们一点一点打磨设计。
在 Agent 循环中仍然能实现吗? 至少,这次实验完全没有证明 TDD 运行在设计上更胜一筹。我甚至开始根据 Opus 的评分怀疑,TDD 是不是反而让设计更差了——毕竟非 TDD 方案更常被排到前面,而且 Opus 指出的那些设计缺陷,在我看来也确实成立。不过数据集当然还是太小,不足以得出任何确定性结论。(如果有人时间和 Token 都够,愿意跑一个更大的实验,那会非常有意思!)
人类先写测试时,会被迫在实现之前先考虑用法;在还不知道怎么构建之前,就得先忍受"把行为和预期定下来"的那种摩擦。Agent 感受不到这种摩擦,它可以在规划实现的同时,随手就把测试写出来。如果两者之间少了人类这个检查点,先写测试的意义还剩多少?
小步骤:YAGNI
只写恰好够通过下一个测试的代码,这本身讲的是克制。它本应阻止我们去造没人要的抽象、去处理没人要求的用例。
在 Agent 循环中仍然能实现吗? 这是一种高度以人为中心的好处,当 Agent 独自执行 TDD 时,它就流失了。我们不再有机会待在那样的摩擦里,去认真琢磨正在构建之物的种种繁复之处。理论上,这种摩擦转移到了"我们给 Agent 写规范"的阶段;但在那里,我们没有类似 TDD 的机制能帮我们小步小步地把规范想清楚。
难道 Agent 就不能小步前进,并在发现可能有不需要的东西时来问我们吗?以我的一般经验来看,它们并不擅长这个。实验里也是一样:"最小实现"的指令并不能可靠地拦住它们多做。它们常常用力过猛,实现得比当前测试要求的更多——毕竟完整需求就摆在它们面前。我们通常不会把规范一条条喂进去,那样太低效了。
小步骤:快速、局部的反馈
一步只走一小段,意味着测试失败时,我几乎能确定问题出在哪里——因为自上次绿色状态以来,唯一变动的就是刚刚写下的那一点。
在 Agent 循环中仍然能实现吗? 这次实验设置没能说明:有无 TDD,Agent 卡在调试里的频率是否不同。但按我的一般经验,Agent 通常相当擅长弄清测试为什么变红,哪怕它们并没有小步谨慎地走到这一步。我仍然存疑:它们卡住时,小步 TDD 是否真能带来有意义的缓解?整体算下来成本/收益是否划算?
小步骤:信心和学习
Kent Beck 在《测试驱动开发:示例》一书的序言里,给出他主张 TDD 的最大理由:"管理恐惧"。他说,对难题的合理恐惧会让开发者变得犹豫、不愿沟通、回避反馈。有了 TDD,每通过一个测试,我们就看到实实在在的进展,于是可以放心——因为进展已经被锁定。测试是一种心理机制,帮我们继续走下去。
在 Agent 循环中仍然能实现吗? 这很大程度上是在管理人的恐惧、给人一个"可以放松"的许可。当 Agent 在循环里做 TDD 时,这一切就无从谈起——它给不了我那种自己一步一步做 TDD 时才能获得的掌控感和信任感。
成本
至少 3 倍的 Token
详细数据见附录。
这很自然:TDD 工作流需要更多轮次和工具调用,Token 消耗自然更高。不过其中相当一部分会命中缓存,所以请注意:Token 量是 3 倍甚至更多,并不直接等于成本也增加了那么多。(遗憾的是,我在实验期间没有记录缓存命中数据。)
提示词维护和测试
TDD 对模型来说似乎并不是一个"顺其自然"的过程,就像是在跟训练数据较劲;要把提示词迭代很多遍,才能让模型在大多数时候照着这套流程走。举个例子:第一批做完后,我意识到 Agent 在红-绿-重构循环里几乎不怎么重构,于是就改了提示词,更加强调这一步——毕竟它对 TDD 至关重要。我后来让 Opus 回看这些会话,看它是否观察到重构有所改善。它确实报告重构步骤增加了——不过它也举出一些例子:Agent 着手重构,却认为设计已经够好而作罢,哪怕 Opus 明显觉得还远远不够(比如所有东西都堆在一个大模块里,明明可以按职责拆开)。
TDD 是一套相对复杂的指令,变量很多,Agent 对它的解读自然也会五花八门。所以我猜,这类提示词在不同模型之间的稳定性,会比更简单的指令更差;换句话说,想让它跨模型、跨版本持续生效,需要花不少功夫。

我的结论
我认为到了今天,已有越来越多证据表明:在"我们希望模型怎么做"这件事上要求得过细,并不是一种可持续的做法。相反,我们应该想尽办法去监控结果、提供反馈。这种反馈要尽可能自动化;同时,我们得仔细想清楚:自己应当在哪些环节介入,充当"什么算好、什么算对"的仲裁者。
我清楚自己的小评估远不足以代表对 TDD 有效性的全面观察,但它绝对没有给我任何新迹象,让我觉得这般折腾是值得的。尤其是当我们还能用其他办法拿到 TDD 的大部分收益时。
我个人已经不再让编码 Agent 先写测试了,更别说做 TDD(说实话,这事我从来没干过)。除非我看到评估数据或其他有力论证,否则我不会改变主意。我转而把精力放在"自己在 Agent 循环之外使用 TDD"所获得的收益上,并探索用别的方式达成这些收益。
如何获得好的回归测试?
……以便 Agent 和我在现有功能被破坏时得到信号
我仍然在意可靠的回归测试:尽管 Agent 完全可能用错误的方式把红色测试"修绿",但至少红色测试给了它一个反馈信号,让它回头核对那些可能被破坏的既有需求。我用变异测试来监控、改进回归质量,而不是精心设计一套 TDD 指令然后祈祷有好结果。
如何将定期重构融入流程?
……以便代码库保持易于修改
重构依然至关重要,但传统 TDD 那种小步走的方式,放在 Agent 循环里似乎既不高效率,也不够有效。举几个可以触发重构的做法:给 Agent 静态代码分析的访问权;定期做结构和模块性审查;建立团队惯例,保持对代码库的良好理解、尽早发现偏离;盯住每次变更涉及的文件数量走势,以及单次变更消耗的 Token 数。
如何获得信心?
……好让我不怕往生产环境推送
最难的问题依旧是:怎么找回 TDD 曾经给过我们的那种信心?怎么管理恐惧?怎么锁定进展?我没有明确答案,但可以提一个看起来不错的积木:我最近试了试 Ivett Ördög 一直在倡导的 Approved Scenarios 方法。用我自己的话讲(别让她为这个说法负责),这算是一种半手动的测试方式,背后是每个应用各自定制的测试运行器。这个运行器会把功能测试场景摆在我面前,让我很容易想清楚,并允许我在彻底确认之后,把那些期望(场景/测试夹具)在该运行器里"冻结"。将来一旦这些被冻结的期望被打破,我就得重新批准一次。我的同事 Matteo Vaccari 对他用这套方法的心得做了一个很棒的介绍。
无论未来究竟是靠什么,让我们重新对软件建立起信任和信心——我认为,我们所熟知的那种 TDD,其角色都会比 GenAI 时代之前小得多。
附录:根据 Opus 评估的结果
你可以在这个仓库中找到完整结果。
- NT = 无 TDD 指令
- T = TDD 指令
- TF = 测试先行指令
所有批次的 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 |
只有中型任务额外加入了测试先行指令。
注意: 这些数字只是会话成本的粗略近似,并不衡量方案里写了多少代码或经过多少思考。记录 Token 用量的脚本基于 pi-coding-agent SDK 的 getSessionStats(),它把会话中每个助手轮次的 input + output + cacheRead + cacheWrite 用量相加。这是对整个对话的累计求和:每一轮都要重新读取已经累积的上下文,每次重新读取(通常大部分由缓存命中)又会在该轮的 cacheRead 里再算一次。所以"总 Token"更多反映的是会话经历了多少轮,并按当时上下文已经长到多大来加权。它把便宜的缓存读取 Token 和昂贵的新 Token 按同样权重计算,因此很可能高估了 TDD 的真实开销。鉴于样本量很小,请把这些倍数当作方向性参考:TDD 确实稳定地贵出数倍,但具体贵几倍则浮动很大。
中型任务,第 1 轮
任务: 构建一个 4 阶段 Python 管道(解析 → 聚合 → 格式化 → 验证),将原始的 ROW_ID:CATEGORY:VALUE:PERIOD 字符串转换为纯文本报告。
数据:
| ID | TDD | 测试数量 | 覆盖率 | 变异测试得分 | 总 Token | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| 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 完全缺失 |
(这一轮的排名只依据综合评判、测试数量、覆盖率和变异测试得分——Opus 的设计/代码/测试分项评分自下一轮起才引入。)
中型任务,第 2 轮
任务: 与 01-medium 相同的 4 阶段报告管道,以更严格的 TDD 遵循度重新运行,并新增了测试先行变体(NT1/NT2 沿用 01-medium 的同一代码库)。
| ID | 方法 | 测试数量 | 覆盖率 | 总 Token | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|
| NT1 | 无 TDD | 107 | 100% | 703,159 | 21 | 20 |
| TF2 | 测试先行 | 90 | 92% | 619,531 | 27 | 26 |
| NT2 | 无 TDD | 75 | 100% | 769,814 | 31 | 30 |
| T2 | TDD | 29 | 98% | 2,099,280 | 96 | 95 |
| TF1 | 测试先行 | 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 | 测试先行 | 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 | 测试先行 | 6 | 6 | 6 | 6.0 | 348 / 360 |
| T1 | TDD | 6 | 6 | 6 | 6.0 | 142 / 228 |
| ID | 方法 | 排名 | 评判 |
|---|---|---|---|
| NT1 | 无 TDD | 1 | 测试套件最深入,验证最干净(复用了格式化器的布局);但 HEADCOUNT 被混入美元总额,没有测试覆盖到这一点 |
| TF2 | 测试先行 | 2 | 全程使用 Decimal,解析/验证很强;但检查 3 是不可达的死代码,验证模块也略显杂乱 |
| NT2 | 无 TDD | 3 | 设计干净,用 Decimal,解析正确;但测试偏薄,有一个无操作测试,验证逻辑自引用 |
| T2 | TDD | 4 | 快乐路径干净,格式化正确;但遇到格式错误的输入会直接崩溃,而不是返回结构化的解析错误 |
| TF1 | 测试先行 | 5 | 单文件方案里解析器最强;但 TOTAL 行损坏(把人数当成美元),交付物里还带着死脚手架 |
| T1 | TDD | 6 | 最紧凑(142 LOC);遇到格式错误的输入会崩溃,验证最同义反复,测试套件最薄 |
中型任务,第 3 轮(改进的 TDD 指令)
任务: 与 01-medium 相同的 4 阶段报告管道,使用改进的 TDD 提示词(强调前置设计和重构)重新运行;NT1/NT2 再次复用 01-medium 的代码库。
| ID | TDD | 测试数量 | 覆盖率 | 变异测试得分 | 总 Token | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| 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 | 是 | TOTAL 行只有 HEADCOUNT,却被打印成 $;验证只是子串检查,不是算术检查 |
| 2 | NT1 | 否 | 带小数的 HEADCOUNT 会让有效输入误报 ValidationError;测试依赖 monkeypatching |
| 3 | NT2 | 否 | NaN/Infinity 会让管道崩溃而不是抛出 ParseError;验证逻辑重新运行格式化器 |
| 4 | T2 | 是 | 验证同义反复(拿聚合结果和它自身比较);宽度检查只是句注释 |
小型任务
任务: 构建一个 Python 模块,验证 DAY-TIME-ROOM-CHECKSUM 格式的医疗预约时段代码,返回一个结构化结果,指出哪个规则失败以及原因。
| ID | TDD | 测试数量 | 覆盖率 | 变异测试得分 | 总 Token | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| 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 忠诚度积分引擎,具备分级积分赚取率(青铜/白银/黄金)、用于等级重算的滚动 365 天消费跟踪,以及积分兑换功能。
| ID | TDD | 测试数量 | 覆盖率 | 变异测试得分 | 总 Token | 轮次 | 工具调用 |
|---|---|---|---|---|---|---|---|
| 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 完成资料调研、把想法组织成结构,并对语言进行润色。
- 原文链接: martinfowler.com/article...
- 鸿途知科网 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明
本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。
鸿途知科网
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。