dev.to 上前阵子有篇 Dimitris Kyrkos 写的文章,标题挺冲:AI 生成的测试没在测你的代码,它在测模型的盲区。底下四十多条评论,一帮人拿自己的经历在对账。我一路刷下来,越看越觉得它戳中的东西根本不在"测试是不是 AI 写的"这一层。
先撂判断:同一个模型同时写实现和测试,毛病不出在作者是一个人,出在这两样东西来自同一套世界观。作者身份是表象,相关性才是本体。这一点要是搞混,后面所有补救方案都得做偏——我自己就做偏过一回。
来,画开看看。
┌─────────── 同一个模型 ────────────┐
│ │
│ 先攒出一套隐含假设 │
│ (日期永远是 UTC 的 ISO 字符串? │
│ 数量永远是正整数? │
│ 空值等于 None 还是空串? │
│ 时区到底按谁转?) │
│ │ │
│ ├────► 按这套假设写实现 │
│ │ │
│ └────► 按同一套假设写测试 │
│ │
└───────────────────────────────────┘
│
▼
套件全绿,一起上线
这张图想说的就一件事:模型写测试的时候,不会重新去推一遍正确行为。它写实现时用哪套假设,写测试时还是那套。所以绿只证明"实现和自己自洽",不证明"假设是对的"。
打个比方:同一个测量员,拿着同一把来路可疑的卷尺,去复核他自己量出来的房间尺寸。他量了两遍,两遍对上了。可卷尺要是歪的呢——两遍一起歪。
比喻在这儿漏风,我得标出来:测量员好歹是对着那个真实的房间量的,模型不是。模型是对着它脑子里"房间该长什么样"量的。它可能压根没去看那个房间。
文章里举了个折扣函数。模型默认数量都是正整数,实现里就不写负数检查,生成的测试也只喂 1、5、100 这种正常值,从来不问 -1 或者 0 该返回什么。行覆盖率 100%,分支覆盖率 100%,然后线上的缺陷照发不误。
我第一版的想法特别朴素:那就换个模型写测试呗。这个模型写的实现,另一个没参与过的模型来写断言,这不就独立了?
等等等等,先别急。评论区 Edward Izgorodin 直接把这条堵死了:换第二个模型,它照样会挑 1、5、100。因为决定输入集合的是"常识",而常识是共享的——跟谁执笔没关系。作者自己后来也认了,说这个修复在折扣例子上会失效,相关性来自"决定输入集合的那层判断",不是断言由谁书写。
得,我这版补丁当场报废。
重来。把人类介入点往上游挪一格:
── 原来的介入点 ──
实现(AI)──► 测试(AI)──► 人 review 断言
↑ 太晚了
── 挪上去之后 ──
人先从规格里点名输入类别:
数量能不能为零?负数?小数?缺失?
│
▼
AI 生成实现 + 生成测试数据
│
▼
机械地检查:每个类别里,套件有没有一个输入落进去
│
▼
再 review 断言
关键词是"机械地检查"。不是让 AI 再判断一遍覆盖够不够——它还会用同一套常识——而是人先把类别列出来,然后一个一个数:负数这一类,有没有落进去?空数组这一类呢?重复键这一类呢?这个是数出来的,不是判断出来的。
评论区里 Heinrich Neb 那条我看的时候坐直了。
他写过一个跨流分配工作的函数,连测试一起。两边都过。后来他把算法整个换成一种故意错误的分块方案——测试还是全绿。到这儿就清楚了:那个测试不是在独立验证行为,它是在把实现里那套计算复述了一遍。
他给出的规则很短:写好保护性测试之后,故意把被保护的对象弄坏,确认测试会红。没红过的测试,不算证据。
✨ 这一下咔哒扣上了。我之前一直隐隐觉得"测过了"是个状态,现在看它其实是个动作——你得让它红过一次,它才算数。作者自己也说,他每次写完测试都会去违反一下那个不变量,看它叫不叫。这大概是全场最便宜的一次现实检查,一分钱不花,就是把代码改坏。
评论区还有人提到 mock 那一层更阴——实现和 mock 一起由模型写的时候,mock 会把模型调用链里的假设整个复制下来。真实库里那个字段是 null、或者大小写差一点,套件照样全绿。这个我也认同,但我觉得它跟主干是同一个病的不同部位:mock 就是把世界观往外搬了一层。
往回退一步。为什么覆盖率工具报了 100% 还漏 bug?
因为覆盖率这东西压根不是为"发现共同盲区"设计的。它只统计哪些地方被走过了,不统计走过的时候手上拿的什么。null、空数组、重复键、类型边界,可以在整份套件里一次都不出现,覆盖率数字一点不受影响——因为它数的是路径,不是输入。
所以那串绿色的百分比看着像在给你背书,其实它只说了一句"这些行被执行过"。执行不等于验证,这句话我原来以为是废话,现在才觉得它是这套东西的枢纽。
留个自检,就一个动作:你手上那个套件,随机挑一条实现里的边界检查,把它删掉。跑一遍。红了,它算证据;不叫,它只是条绿色的声明,在 CI 里跟通过长得一模一样。
这个检查太便宜了,便宜到没人做。
下期想画开哪个概念,你说了算。
本期画开:相关性不在作者身份上,在世界观上。