文章认为 AI 辅助开发改变了传统测试金字塔的经济性,提出「测试火箭」新模型:AI 让端到端测试成本大幅下降,单元测试不再是主角,集成和 E2E 权重上升。
每隔几年,就会有人重新画一张测试图,给它起个新名字。测试金字塔、测试蜂窝、测试奖杯:每一个的出现都是因为软件开发的经济学已经发生了足够大的变化,以至于旧图形开始变得不合时宜。
我认为 AI 辅助开发又一次改变了这一格局,而这一次,形状看起来不像金字塔,更像一枚火箭。所以我就叫它——测试火箭(Test Rocket)。
经典测试金字塔的观点是:写大量快速、廉价的单元测试,少量集成测试,只有极少数的端到端测试。这个逻辑在很长一段时间内都站得住脚。单元测试编写快速、运行也快,而任何处于技术栈更高层级的测试往往更慢、更贵,也更脆弱。

这种情况一直运转良好,直到应用程序变得更加分布式。一旦你的系统变成一堆互相通信的微服务,大多数有趣的 bug 不再存在于单个函数内部,而是开始出现在组件之间的缝隙里:这里是一个序列化不匹配,那里是一个超时假设出了问题。
Spotify 在微服务实践中遇到了这个问题,并提出了测试蜂窝。他们的观点很简单:一个服务的真正复杂性在于它如何与数据库和其他服务通信,而那些把所有这些都 mock 掉的单元测试,最终测试的是实现细节而不是行为。所以他们更侧重于集成测试,并有意保持单元层较小。
Kent C. Dodds 在前端工作中也提出了类似的观点,即测试奖杯:静态分析作为底层,然后是一个相当薄的单元层,集成测试在中间承担大部分重活,最后是薄薄一层 E2E 在顶部。

这里有趣的操作与单元测试本身的好坏无关。它是对"集成测试一定又慢又贵"这一假设的挑战。现代工具让集成测试足够快,可以广泛使用,而且由于它们一次锻炼多个组件,往往每个测试能捕获更多真实 bug。
蜂窝和奖杯都在表达同一个底层观点。当你所关心的行为只有在组件交互时才会显现时,一堆孤立的单元测试是一笔糟糕的投资。
金字塔、蜂窝还是奖杯都没有考虑到一个写它们时根本不存在的东西:单元测试对于 AI 编码 agent 来说可以是一个非常快速的反馈循环,而不仅仅是对人类的安全网。
观察一个 agent 工作一段时间后,模式就变得清晰了。它编辑一些代码,运行大量单元测试,读取失败结果,然后修正。然后再来一次。又来一次。

这个循环改变了单元测试套件的用途。它不再只是一个开发者维护来日后捕获回归的东西。它是 agent 自己工作循环的一部分,每秒钟告诉它刚刚做的改动是否真的做了它应该做的事。
这就改变了成本等式。编写和维护一百个额外的单元测试过去意味着投入一百个人力单位:有人必须编写它们、审查它们、保持它们通过。Agent 可以几秒钟生成一个有用的测试。所以旧问题"我们能承受这么多单元测试吗"在很大程度上不再是正确的问题。现在重要的是:这些测试真的告诉了 agent(或审查者)关于行为的什么真相?
测试火箭保持了奖杯相同的四层结构(静态分析、单元、集成、E2E),但转移了重心的位置。

这个名字不仅仅是装饰。静态分析是推力基座:广泛、不起眼,做着防止火箭倾倒的枯燥工作。单元和集成测试构成箭体,两个完整级而不是一个薄一个厚,因为两者现在都在做真正的推进。没有一个是设计成事后才想起来的。而 E2E 位于顶部作为鼻锥:小而关键,但不是质量和动力所在。
火箭的重点不是一个你要达到的具体比例。它是:
构建真正的单元和集成反馈循环,并保持更高层级专注于只有它们能给你的那种信心。
静态分析也不会保持不变。它是最便宜、最快的检查点,甚至比第一次单元测试运行还快,所以它是捕获 agent 错误而不消耗完整测试周期的自然场所。与 agent 协作的团队更依赖它:更严格的类型检查、针对 agent 总是忽略的内部约定的自定义 lint 规则、依赖和安全扫描、对生成代码的复杂度门槛。同样,单元层增长的原因也是一样的:它快到可以对每次变更都运行。
单元测试是这种转变真正显现的地方。在 AI 驱动的工作流中,它们是 agent 检查"我刚才是否破坏了什么东西"的最快方式,这使得一个更大规模的单元层变得值得拥有,即使这些测试是完全孤立的。
但"更大"只有在知道放什么进去才有意义。把它想象成不是把蜂窝或奖杯的单元层音量旋钮调大,而是想象成一些新的类别——它们一直值得写,但人类必须做成本收益权衡时很少能存活下来:
边界和边缘情况测试(null、空集合、差一错误范围),它们原则上正确,但当有人必须手工编写每一个时就从未入选。
属性测试,检查一条规则在生成的输入范围内是否成立,而不是人类有时间手工挑选的两三个例子。
每一个 agent 修复的 bug 都有一条永久的回归测试,这样同样的失败不会悄悄卷土重来。这是团队一直打算一致去做但很少做到的。
特性测试,在重构前锁定现有行为,这样 agent 和审查者都有一个安全网来应对那些曾经风险太大不敢尝试的变更。
围绕 agent 正在积极修改的部分的窄粒度、单行为测试,这样失败就能精确指向哪里坏了,而不是还需要追踪的广泛集成失败。
这些都不稀奇。它们是细心的开发者一直知道值得拥有但很少有时间写的测试。一个好的单元测试的想法没有变。变的是这些特定类型终于足够便宜,真的可以存在了。
集成测试仍然在做单元测试做不到的事:证明真实的数据库、真实的 HTTP 边界、真实的消息队列实际上能协同工作。AI 在这里也有帮助,更快地生成 fixture 和测试容器,但搭建一个真实的依赖并等待真实的 I/O 不会像单元测试的创作成本那样崩塌。这就是为什么这一层不需要自己的"新东西"列表:它在蜂窝和奖杯下已经相当可观了。火箭不会为了资助单元层而牺牲它,而是让两者都保持可观的规模。
E2E 测试保持在顶部,验证那些最重要的少数用户旅程。廉价的生成不会像对单元测试或静态分析那样改变它们的 calculus,因为它们的成本从来不在于谁来编写它们。成本在于运行本身:真实的浏览器、真实的后端、分钟级而不是毫秒级的执行,以及无论多少 AI 辅助编写都修复不了的 flaky。Agent 可以像写五个一样轻松地写五十个 E2E 脚本,但在每次变更后运行全部五十个仍然很慢。它们足够昂贵,所以你不想让它们承担单元和集成已经覆盖的重量。
问得好,旧的批评在一种特定方式上仍然成立:一个糟糕的单元测试仍然是一个糟糕的单元测试。如果它在断言实现细节、mock 每个协作者、每次重命名变量都破坏,或者纯粹是为了把覆盖率数字推上去,那么 AI 更快地编写它并不会让这些问题有任何改善。
真正改变的是经济学,而不是判断测试好坏的标准的本身。反对大单元层的老论点是关于人类时间的:编写和维护一大套孤立的测试不值得,因为集成测试每个测试能给你更多信心。一旦生成有用的单元测试的边际成本下降,而且它作为 agent 即时反馈的价值上升,那笔账就不像以前那样算了。
所以不,测试火箭不是偷偷穿着新帽子的金字塔。它不是在争辩单元测试应该再次占主导,也不是在争辩要一个具体的 50/50 比例——那不过是把一个武断的比例换成另一个武断的比例罢了。它的论点更窄:单元层和集成层都不应该再是薄的那一层了。蜂窝和奖杯各选了一个来保持薄,假设另一层的大量内容在生产和维护上都很昂贵。一旦这个假设对单元测试不再成立,就没有了让任一层刻意保持薄的好理由。两者之间的确切比例应该来自你的 bug 实际所在的位置,而不是来自过去编写任一层有多贵。
每一层仍然在回答不同的问题:
单元测试:这个特定局部行为刚才坏掉了吗?
集成测试:真实的组件实际上能协同工作吗?
E2E 测试:用户关心的旅程端到端仍然能用吗?
静态分析:这代码在结构上是否健全?
这是我会对自己的想法稍微push back的地方,因为这里有一个真正的陷阱,而它不是人们通常首先想到的那个。
显而易见的担忧是成本:团队因为现在很便宜而大量产出低价值测试。更尖锐的担忧是蜂窝最初的那个:大量 mock 的、孤立的单元测试与实现细节耦合,这使得它们在重构期间积极有害,而不仅仅是编写上的浪费。廉价的 AI 生成测试在让它变好之前先让它变糟了。一个看着自己刚写的代码的 agent 有充分的动机生成一个镜像该代码结构的测试,因为那是通往绿色最快的路径。更大、更便宜的单元层可以轻易地重新引入蜂窝警告过的那种失败模式,只是以更大的量——尤其是当写实现的那个流程同时也写测试并给自己打分时。一个从它正在检查的代码派生出来的测试会乐于断言"代码做了代码做的事",这完全没有说明它是否做了它应该做的。
而把每个生成的测试都交给人工审查并不能解决这个问题。它只是把瓶颈移到了一个不那么显眼的地方,并杀死了速度——而速度才是整个问题的关键。修复必须是自动化的:
从规格而非代码派生测试。从功能应该做什么生成,而不是在从未见过实现的那一 pass 中。这是直接回应蜂窝的:一个从未见过代码的测试不会与它耦合。
从 agent 在写代码前承诺的计划派生测试。比规格派生的独立性保证更弱(同样的推理写了这两者),但作为第二个信号很有用。
先写测试再写代码(TDD)。如果测试必须在实现"完成"之前通过,它就不可能被塑造成匹配一个还不存在的代码。这将规格优先的独立性烘焙进了工作流本身,而不是依赖纪律来保持两个 pass 分离。
使用第二个对抗性 agent 来寻找测试和行为之间的分歧,而不是信任实现自己的上下文为其自己担保。
运行变异测试,偶尔。它确认套件确实对注入的故障失败,但每个变异运行完整套件会很快变得昂贵。把它当作对变更代码或高风险路径的有针对性的、偶尔的检查,而不是 blanket policy。上面那种对抗性 agent 方式作为默认方式扩展性更好。
人工审查仍然在检查规格方面有作用,但不能作为主要关卡。那个关卡需要与它正在检查的 agent 一样快地移动。
金字塔为成本优化。蜂窝和奖杯为信心和在高边界捕获失败优化。火箭增加了一个第三个要优化的事:套件如何很好地为一个持续生成和重写代码的 AI 提供信息。
这改变了快速层的价值。静态分析成为一个更强的第一检查点,在它们消耗一个测试周期之前捕获廉价的结构错误。单元测试提供快速的行为反馈,而集成和 E2E 测试提供逐步更高置信度的验证。
单元测试作为一个想法没有变得更好。它们被用来做什么,以及生产它们的成本,在底层发生了变化。
更多测试原来是答案的一部分,但它们从来不是答案本身。答案是一个快速的、分层的系统,给 AI 足够的信号来快速移动,并给工程师足够的信心来真正按下部署。
这就是测试火箭 🚀。