如果你给 AI 智能体一种独立验证其工作的方式,就能大幅提升输出质量。智能体写出的代码“在规模上要好得多”。这里的“在规模上”来自于验证。智能体让检查变得便宜。
过去“好代码”成本高昂的部分(测试、基准测试、规格说明、真正的端到端运行)现在变成了你可以向智能体索取的廉价资源。你可以改进检查的方式。
所以我们的工作不再是“逐行审查”,而是“构建智能体可以自行运行的循环”。给智能体一种验证工作的方式。这个循环就是质量产生的地方。
还要让品味产生复利效应。当智能体出错时,把它记下来(共享配置、钩子、测试),这样就不会再犯同样的错误。人类仍然拥有最终决定权,但现在他们在审查证据,而不仅仅是代码差异。
要让你的智能体产出高质量输出,就要给它配备检查工作的方式。测试是其中的重要部分。
以下是一些我认为值得投入的测试类型。
示例测试说明应该发生什么;属性测试说明什么必须不发生。

如今的模型已经足够好,主要瓶颈不再是写代码,而是验证所写代码是否正确的能力。
如果你的智能体无法验证其输出,它本质上就是在猜测,而错误的猜测最终会由你的测试套件来验证。也就是说,你会陷入一个反馈循环:反复来回,直到你能阅读代码差异、运行测试、检查应用、发现 bug 并报告回去。这是最慢、成本最高的反馈循环,对于超过一个人来说不可扩展。
但如果你的智能体能测试自己的输出,它会写一个改动、运行检查、读取输出、不断尝试,直到能通过检查。这发生在任何人类看到任何内容之前。自我验证的智能体与人类主导的反馈循环之间的质量提升怎么强调都不为过。我始终更偏好一个更好的反馈循环,而不是一个稍微更好的模型。

这是对测试的新定义。多年以来,测试是人类识别 bug 和可靠重构的主要工具。这些仍然是编写测试的好理由,但测试也是你的智能体感知世界、知道自己何时完成的方式。
并非每个测试对这一目的都有同等的价值。
如果只能选择一种测试,那就是端到端测试。它们描述真实用户的操作。
端到端测试是人类会做的事情:注册、添加商品到购物车、结账、看到确认信息。它忽略代码的内部结构,只看用户能看到的东西。
这是你希望智能体拥有的那种神谕。机器经常会写出通过检查但忽略大局的代码。基于 mock 的单元测试可能通过,但功能可能是坏的,因为智能体和你的测试有共同的理解错误。另一方面,端到端测试很难被欺骗。要么整个流程正常工作,要么不工作。

是什么让端到端测试对智能体特别有价值,而不仅仅是用于 CI/CD 管道?
重要的是要说明,端到端测试有其真实的权衡:它们比传统单元测试更慢、更不稳定。选择的不是消除它们,而是偏好一小套高价值测试。
注意:端到端测试很好,但 Lauren(@poteto)强调让智能体在真实运行的系统上拥有手和眼是值得的:一个维护良好的技能/CLI,知道如何启动、驱动和观察应用(CDP、模拟器、API 工具等),外加一张功能地图。功能地图是用户视角的清单:存在哪些功能、真实用户如何到达它们、键盘快捷键/选择器、预期结果和注意事项。
许多测试关注的是人类认为可能出错的地方。属性测试关注的是人类没有考虑到的地方:智能体出错的情况。
人类在测试中写的很多内容都是关于示例的:“如果这是输入,那么输出应该是这个。”这些测试擅长确保某事应该工作,但不擅长确保某事永远不会失败:人类想不出所有会失败的输入。

属性测试反转了前提:你创建一条应该成立的规则,然后一个工具会生成数百或数千个输入来打破它。像 JS/TS 的 fast-check、Python 的 Hypothesis、Rust 的 proptest 这样的工具在这方面做得很好。当发现失败时,输入会被缩减为打破测试的最小用例:它不会因为随机数据块而爆炸。
以下是几乎每个软件项目中常见的属性:
当你使用智能体时这很关键,因为智能体会根据其内部逻辑编造自己的成功测试示例。属性测试迫使智能体处理任何生成的输入,而不仅仅是它选择的输入。
注意:属性测试和差分检查覆盖了很多,但 Claude Code 的 Boris Cherny 一直在最难的状态机、并发和数据流部分使用模型(Lean、TLA+)。简短的提示可以通过构建模型、找到反例、复现问题并修复来暴露竞态条件和微妙的 bug。
属性是表达需求的好方法。如果我说购物车总价必须始终等于其行项的总和,这是一个很好的规格说明。智能体可以用这个规格说明来构建自己的代码,而生成器可以寻找差异。
重写的典型失败模式之一是它能够通过新的测试套件,但与旧系统在许多小的、未被记录的方式上存在分歧。这是官方规格说明与实际行为之间的差异。用户依赖的是实际工作的东西,而不是规格说明说应该工作的东西。
差分测试弥合了这一差距。不依赖新的测试套件,而是用相同的输入并行运行新旧系统。如果输出有差异,你要么在新系统中引入了 bug,要么你故意做了一个应该记录为破坏性变更的更改。

这就是选择输入重要的地方。如果你使用手动选择的测试用例,你会错过罕见的边缘情况。随机生成输入、重放实时流量样本,或两者结合。这相当于属性测试,属性是你的新系统输出与旧系统输出一致。
差分测试对于智能体辅助的大规模迁移非常出色。你在新系统调整、运行差分测试、审查不匹配、修复之间循环,然后重新开始循环。因为旧系统是神谕,你不需要向智能体解释每个边缘情况。两个版本都做过并运行差异的团队告诉我,这是一种从修复一个问题又产生另一个问题,到仔细而有条理地逐步进行对比的转变。
测试必须快速且确定性运行,否则智能体会学到错误的教训。
这不仅仅是浪费时间的问题——这是一次训练练习。如果测试缓慢或不稳定,智能体会学会有时候重试测试比尝试修复问题更好。
人类对糟糕的测试基础设施更宽容。我们知道哪些测试是不稳定的,我们知道在失败时重新运行 CI,我们愿意等待 20 分钟来构建一个可能不稳定的东西。然而,智能体从循环的激励中学习。如果重试失败的测试有时能成功,它会把重试作为首选策略。如果运行测试需要 15 分钟,它会减少运行频率,在每次运行之间批量更多的变更,并失去精确指出哪个变更导致失败的能力。
这两种失败模式都会让智能体变得更差,但没有明显的失败迹象,所以你产出的输出质量对人类来说会更低。
以下是我认为“良好”可能是什么样的一些想法——智能体应该从循环的激励中学习。

注意:一些额外的实际习惯包括编码前明确重述目标(“重述你认为的成功标准是什么”)、用新鲜的上下文或单独的审查智能体进行验证,以及把生成的测试本身视为需要审查的产物(这个测试真的能区分正确和看似正确但错误的实现吗?)。
在告诉你的智能体去做什么之前,你必须知道它应该成功完成什么。这可能是一个端到端工作流、一个特定属性,或者与先前系统的比较。无论是什么,确保它是快速且确定性的,你可以让智能体在一个解决方案上迭代。
你的工作不需要阅读每一行输出,但你需要定义成功是什么样的。你从判断中获得了更好的价值,而且它比你自己阅读代码差异的扩展性更好。
当你在测试你的智能体时,你需要有应该发生什么的例子,以及不应该发生什么的属性。要想赢,你的智能体的测试过程需要快速且准确。
