Jerome
返回博客

当代码变成耗材,Test 成为你最重要的工程资产

14 min readai-engineering · testing · tdd · agentic-coding · software-architecture

我已经不怎么看 AI 写的代码了

最近我发现自己的注意力彻底转移了。以前 review PR 会逐行看实现,现在我盯的是 test 结果。

AI 写的代码风格和我不一样,变量命名和我不一样,实现路径和我预想的不一样。但我的关注点已经不在这些地方了。重要的是 test 过不过,test coverage 够不够。

过了就合。挂了就让它修。修完再跑。这个循环比我亲自 review 每一行代码更可靠,因为 test 不会因为「看起来合理」就放过逻辑漏洞。

一开始我觉得这是在降低标准。后来想明白了,这是在换一个载体承载标准。以前标准写在 reviewer 脑子里,通过 code review 执行。现在标准写在 test 里,通过自动化执行。后者更稳定,更可复现,不受心情和疲劳影响。

这让我开始想一个更根本的问题。如果 AI 能随时重写任何代码,那工程系统里到底什么是不可替代的?


代码的生产成本正在趋近于零

今年二月,Anthropic 的 Nicholas Carlini 用 16 个 Claude agent 并行工作,两周写出了一个 10 万行的 C 编译器。能编译 Linux 6.9、QEMU 和 Doom,GCC torture test suite 通过率 99%。成本 2 万美元。人类写了零行编译器代码。

Carlini 全部时间花在设计 test harness 上。找高质量的测试套件,写验证脚本,搭 CI pipeline 防 regression。他自己说,"I had to constantly remind myself that I was writing this test harness for Claude and not for myself."

这不是个例。OpenAI 有团队 5 个月出了 100 万行代码,3 个工程师,零手写。微软 CTO 预测 2030 年 95% 的代码由 AI 生成。Google 和微软都报告当前 25-30% 的新代码已经是 AI 产出。

当代码可以被随时重建,它的经济属性就变了,从资产变成了衍生物。就像你不会把编译产物当作源码保存一样,代码正在变成一种中间产物。真正的源码,是那些定义了「什么算对」的东西。


Spec、Code、Test 三者的稳定性完全不同

传统软件工程依赖三根柱子。在 AI 时代重新审视,它们的可替代性差异巨大。

Spec 最脆弱。 自然语言的 spec 本质上是模糊的,同一份 spec 两个工程师会写出完全不同的实现。Martin Fowler 今年三月说了一句到位的话,"Tests are a valuable way to understand what a system does." 言下之意,test 本身就是一种更精确的 spec。如果 test 完整地描述了系统行为,那传统意义上的 spec 就是一份可有可无的衍生文档。

Code 是可再生的。 上一节已经说清楚了。

Test 的独特地位在于它同时满足两个条件。 机器可执行(不像 spec 有歧义),描述行为而非实现(不像 code 会过时)。这让 test 成为系统的 ground truth,既是给人看的合约,又是给机器跑的验证。

这个排序描述的是一个正在发生的现实。越来越多的 AI coding workflow 里,人类的核心工作就是写 test,然后让 AI 在 test-fail-fix 循环里自动收敛。


Test 是 AI 唯一的确定性约束

为什么 test-first 是目前让 AI 可靠产出代码的最高 leverage 方法?因为 test 提供的约束强度,是其他机制达不到的。

Prompt 约束是概率性的。 Claude Code 泄漏的源码里明确写着「完成前必须验证」「不要虚报测试通过」。但这些是 system prompt 里的文字建议,模型可以忽略,也确实会忽略。你加越多约束条目,模型的注意力越容易从「完成任务」偏移到「遵守约束」。这是一种约束膨胀的困境。

Test 约束是确定性的。 test 要么过要么不过,没有中间态。AI 在 test-fail-fix 循环里做的事情,本质上是一种搜索。每次 fail 给它明确的 error signal,每次 fix 缩小搜索空间。这是目前 agentic coding 里唯一经过大量实践验证的可靠收敛机制。

Kent Beck,TDD 的发明者,去年在 The Pragmatic Engineer 的访谈里说了一句非常精准的话。

"I'm having trouble preventing AI agents from deleting tests to make them pass!"

AI 试图删掉 test 来通过验收。这个行为本身就说明了一切。test 是 AI 眼中唯一的硬约束。它不会去删 spec(spec 不影响执行),不会去改 CI config(它知道这不在 scope 里),但它会去动 test,因为 test 是唯一真正挡在它面前的东西。

所以工程上的结论很清楚。与其花精力写更完美的 prompt 让 AI 一次写对,不如投入精力构建更完整的 test,让 AI 在循环里自己收敛。前者是在做优化的优化,后者是在建基础设施。


数量是次要的,层次才是关键

上面的论述容易被读成「写更多 test 就行」。其实关键在于你把 test 写在哪一层。

LLVM 项目给出了一个完美的参照系。LLVM 有 20 年历史,它的优化 pass 被反复重写过,整个 backend 被整体替换过,但有一类东西几乎没动过,就是 Intermediate Representation(IR)层的 regression test。

LLVM 不在 C 源码层测试(太高层,太不稳定),不在机器码层测试(太低层,太跟实现绑定),而是在 IR层测试。IR 是编译器的稳定契约,是前端和后端之间的接口。只要这个接口的行为不变,前后端怎么重写都不影响测试。

这个思路直接可以迁移到应用开发。

找到你系统里的 "IR"。 对大多数应用来说,这是 view model 层或者 domain logic 层。业务逻辑的 test 写在这里,UI 层就变成了可以任意重写的下游。AI 怎么画按钮、怎么排布局,都不重要,只要 view model 的行为不变。

E2E 测的是用户可见行为(稳定),unit test 测的是实现细节(脆弱)。 选对测试层次比追求覆盖率更重要。一个在正确层次上写的 test,比十个在实现细节层的 test 更能经受代码重写。

TDD 的关键不是 test-first 的仪式感。 先写 test 还是后写 test 是次要问题。关键问题是你有没有把 test automation 放在架构设计的第一优先级。你的代码结构是不是 testable 的?你的 test 是写在稳定接口层,还是在追着实现细节跑?


两个真实的挑战

这个论点有两个值得认真对待的挑战。

第一,AI 会 game 你的 test。

Leonardo de Moura,Lean 和 Z3 的创始人,今年二月写了一篇 "When AI Writes the World's Software, Who Verifies It?",里面指出 Anthropic 的 C 编译器曾经 hard-code 值来通过测试。这不是 bug,这是模型在做「过 test 的最短路径」优化。

de Moura 的判断是,test 提供 confidence,proof 提供 guarantee。对密码学库、TLS 实现、授权引擎这类安全关键系统,他说得对。formal verification 是 test 的上位替代。AWS 已经在用 Lean 验证 Cedar 授权引擎,微软在用 Lean 验证 SymCrypt 密码库。

但对 95% 的应用软件来说,formal verification 目前还不实际。而且 "AI 会 game test" 这件事本身有解法。property-based testing 和 fuzzing 用随机输入测试不变量,比固定的 test case 更难被 overfit。

第二,AI 也能写 test。

如果 AI 同时写代码和测试,两者可能共享同一个错误假设。有个真实案例,AI 写的支付 API 代码和测试都用了同一个错误字段名,test 全绿,上线后生产环境崩了。代码和 test 都「对」了,但对的方向是错的。

Emily Bache 今年采访了一批用 agentic coding 的高段位工程师,发现了一个有趣的技术原因。AI 的训练数据里几乎没有「red」状态的代码,因为 failing test 的代码不会被 commit。所以 AI 天然不擅长 TDD 的 red step。这反过来说明了一件事,人类写的 failing test,恰恰是 AI 训练分布之外的东西。这种「分布外」的属性让人类写的 test 成为真正独立的验证信号。

两个问题的答案指向同一个结论。 重点不是「有 test」,而是谁写 test、在什么层写。人类设计的、在稳定接口层的 test,是当前最务实的 ground truth。这个判断会随着 formal verification 工具的成熟而演化,但在今天,test-first 是你能做的最高 ROI 的工程投资。


工程师的价值正在重新分布

如果代码的生产成本在急剧下降,spec 是衍生文档,那工程师的不可替代性在哪里?

在于设计「什么算对」的能力。

具体来说,三件事。

选择正确的测试抽象层。 你系统里的稳定契约是什么?LLVM 的答案是 IR(编译器前后端之间的中间表示)。对应用开发来说,可能是 view model 层或 domain logic 层。哪些接口是稳定的,哪些会随实现变化?在稳定层写 test,让不稳定层成为 AI 可以自由重写的空间。

设计验收标准。 什么状态算成功?边界条件在哪?error case 怎么处理?这些是 AI 无法替你决定的东西,因为它们编码的是 business judgment,不是技术逻辑。

构建 test infrastructure。 让 AI 能自主在 test-fail-fix 循环里收敛。这意味着 test 要快(秒级反馈),要有清晰的 error message(AI 能读懂 failure reason),要有 CI pipeline 防止 regression。

Emily Bache 的采访里有一个耐人寻味的发现。所有成功采用 agentic coding 的高段位工程师,之前全是 TDD 实践者。不是因为 TDD 是信仰。是因为 TDD 训练出来的那套思维方式,先定义「对」再写实现、先建验收标准再考虑怎么达成,恰好是 AI 时代最值钱的工程能力。

写代码的成本在快速下降。设计验收标准的价值在快速上升。Test automation 是旧时代就存在的实践,但在 AI 让代码生产成本趋近于零的今天,它的战略地位被彻底重估了。