AI 自动生成测试,谁来判断答案正确
测试预言机负责判定输出是否正确,但复杂系统往往无法直接列出标准答案。文章解释这一测试难题及不同判定依据,提醒开发者审视 AI 测试工具如何决定通过或失败。
完整中文译文
有个问题,听起来简单,实际却不简单:程序输出了一个结果,你怎么知道它是对的?
这就是测试中的 oracle 问题,也是软件测试领域最古老、至今仍未解决的问题之一(这一领域的权威综述由 Barr、Harman、McMinn、Shahbaz 和 Yoo 撰写,发表于 2015 年的 IEEE TSE)。你写过的每一个测试,其实都以某种方式悄悄回答了这个问题。而现在,AI 测试工具正在替你作答,这恰恰是我们值得把它弄明白的原因。
oracle 到底是什么
oracle 就是决定测试通过还是失败的机制。当你写下:
assert add(2, 3) == 5
其中的 == 5 就是你的 oracle。你作为人类,知道正确答案,然后把它硬编码进去。很简单。
现在,把规模放大。对于一个包含 10,000 个节点的图,路径规划器的正确输出是什么?涉及 40 条相互影响的规则时,税额计算的正确结果是什么?ML 模型给出的排序呢?PDF 渲染器的输出呢?很多时候,你根本没法直接写出预期答案。这就是 oracle 问题:测试最难的部分不是运行程序,而是知道什么样的结果才算正确。
我们实际如何应对:oracle 分类
我们很少拥有完美的 oracle,因此会使用只能判断部分正确性的 oracle:
- Specified oracle(基于规格的 oracle):由规格说明或参考实现给出确切答案。判断能力最强,也最少见。
- Derived oracle(推导式 oracle):从其他已有材料中推断正确性,例如之前的版本(回归测试)、文档,或运行较慢但值得信赖的实现。
- Metamorphic oracle(蜕变式 oracle):无法检查某个输出时,就检查多个输出之间的关系。你可能不知道
sin(x)的值,但你知道sin(x) == sin(x + 2π)。对于科学计算代码、ML,以及任何缺少真实标准答案的场景,这种方法都非常有用。 - Implicit oracle(隐式 oracle):不需要规格说明,任何人都能识别的“明显”故障,例如崩溃、卡死、内存损坏。成本低,但判断能力弱——“没有崩溃”这个门槛实在太低。
- Human oracle(人工 oracle):由人来目测检查。成本高、难以规模化,而且判断不一致。
大多数实际测试套件,都是一堆隐式 oracle 和基于规格的 oracle 拼在一起。因此,它们能发现崩溃,也能覆盖那些有人想到要写断言的少数情况,却会漏掉那些细微的、看起来合理但实际错误的输出。
为什么 AI 让这个问题变得紧迫
两种变化,如今同时撞上了 oracle 问题:
- AI 编写 oracle。 当 LLM 为你生成断言时,它就在决定什么叫“正确”。而模型特别喜欢生成约束薄弱、只是复述程序行为的断言,例如类型检查、“没有异常”、宽松的取值范围。这些断言看起来像测试,却几乎没有约束输出。一个全部通过、却充斥着薄弱 oracle 的测试套件,不过是换了副面孔的 oracle 问题。
- AI 本身就是 oracle。 “LLM-as-judge”让模型本身成为判断通过或失败的机制。它有点像一个带着主观看法的隐式 oracle:方便、可规模化,但如果不做测量,你就无从检验它的判断是否可靠。(我测过一个:一个本地 LLM judge 放过了大约四分之一确实错误的输出,而它当时的置信度接近 100%。)
这两种情况下,我们都在不知不觉中,把测试领域最古老的难题交给了一个表达流畅、速度很快、却经常出错的系统,甚至没有意识到自己已经这么做了。
实践中该怎么做
明确说出你的 oracle 是什么。对于每个重要的测试,都问一句:这里究竟由什么来判断正确与否?它的判断能力有多强?“它返回了一个字符串”算不上 oracle,只是一种感觉。
没有真实标准答案时,试着寻找蜕变关系。这种方法用得太少,但非常有力。
如果你的 oracle 是 AI,就把它当作一个需要接受测试的 oracle:用真实标准答案检验它,测量它出错的频率,不要相信它的置信度。
oracle 问题从未消失。我们只是造出了能把它掩盖得更像那么回事的工具。意识到它的存在,这场仗就已经赢了一大半。
面对那些最难验证的代码,你是如何解决 oracle 问题的?蜕变测试?参考实现?我很想听听大家的做法。
我是 Krishnasai Rollamadugu,一名测试自动化架构师,正在研究 AI 驱动的软件测试中的信任与测量问题。
如需进一步采取行动,你可以考虑屏蔽此人和/或举报滥用行为。