前几天在知乎看到一个案例,一位叫"AI程序员老陈"的开发者说,他让 Codex 给一个存量计费模块补单元测试。AI 给得又快又漂亮:几十个测试用例,覆盖率从五十多直接干到 90%,报告一片绿。知乎
他不放心,顺手把核心费率计算函数里的一个乘号改成了加号,重跑测试。
结果:所有测试全过,一个没红。知乎
那几十个用例,压根没在验算出来的钱对不对,全在断言"这个函数能调通、返回值不是 null、字段都在"——测的是"程序没崩",不是"算得对"。这种测试,覆盖率刷到 100% 都没用,线上该错还错。

这不是孤例。整个 8 月,知乎上密集出现了一簇类似讨论。一位大厂质量方向的作者直接写了篇《AI 写的单元测试,绿得没有含金量》:上线第二天,一个明显的空指针漏了出去,AI 写的测试一个都没拦住。知乎另一个热帖"为什么用 AI 写代码之后,人反而越来越累了"下面,被反复引用的一句话是:测试通过不等于代码正确,AI 代码的测试通过率是会骗人的。知乎
为什么 AI 特别容易写出这种"假绿"测试?根子在一句话上:AI 是照着你的实现代码写测试的,不是照着"这段代码本该干什么"写的。
实现里怎么算,它就断言怎么算。你代码里乘号写错了,它连测试一起给你写错,还理直气壮地全绿。这等于让 AI 自己给自己的作业盖章——它怎么可能盖不合格?知乎
顺带说下单元测试的本分:测试只聚焦单元自身的逻辑,数据库、网络、第三方这些外部依赖,用测试替身(Mock/桩)隔离掉。问题在于,AI 经常把被测逻辑本身也一起"隔离"了。

把社区讨论里的翻车案例归归类,AI 测试的"假绿"基本超不出这四种模式:
"没崩"式断言。只断言 status == 200、返回值不为 null。真实案例:下单接口测试断言了 200,但返回体里金额是 null、订单状态还停在 created 没变 paid——功能早挂了,测试绿着。知乎
恒真断言。assertTrue(user != null),可这个 user 是刚 new 出来的,当然不为 null。更隐蔽的是断言 Mock 对象的输入——“我传了什么”,而不是"代码处理后产出了什么"。
Mock 过度。把支付服务整个 Mock 掉,测试完美通过,上线才发现真实网关返回的 amount 是字符串,代码却按数字比大小——真实环境的坑被 Mock 严严实实盖住了。原则就一句:Mock 只用于隔离外部不可控依赖(网络、第三方、时间),被测逻辑本身一律不 Mock。
只测阳光路径。正常下单测得满满当当,覆盖率 90%+,但"库存不足"“余额不够”“网络超时"一条用例都没有,生产环境第一次触发就是 500。覆盖率工具只数"这段代码被执行了没有”,不数"这条失败路径被验证了没有"。
那怎么验证?有个一分钟就能上手的土办法。
把被测代码故意改错一行——把 > 改成 >=、+ 改成 -、返回值改个数——然后重跑测试。测试挂了,说明它真在测;还是一片绿,说明这堆测试是摆设,删了都比留着误导人强。
这招的学名叫变异测试(mutation testing),业界有成熟的开源工具:Java 的 PIT、JS/TS 的 Stryker、Python 的 mutmut,可以在 CI 里批量往代码里注入变异,看测试能抓住多少。知乎不想引入工具,手动"戳一下"同样成立。就像老陈说的:别信覆盖率数字,也别信"全绿"——戳一下代码它能疼(测试变红),这测试才是活的。
再看两眼断言:预期值是不是你手算出来的常数,而不是从实现里抄的;每个 raise/throw 的异常分支,有没有对应的失败断言。在成熟团队里,CI 的测试结果摘要本来就是合并门禁,绿了放行、红了拦截——前提是,那个绿是真的绿。

当然,公道地说,AI 测试不是一无是处。搭测试骨架、填模板样板、批量补等价类用例,AI 是真的快、真的省,这一点人比不了。而且有一种声音正在变大:AI 编程时代,单元测试不是变得不重要,而是更重要了——测试正在变成 AI 自我校验代码的"验收标准"。知乎关键从来不是要不要让 AI 写测试,而是 AI 写完测试之后,瓶颈从"写"挪到了"验"。
这里给一份可以直接贴进 PR 评审的清单,AI 生成的测试合入前,逐条打勾:
每个用例都断言了业务结果(返回值关键字段、DB 副作用、状态变更),而不只是状态码?
每个用例都有"非平凡断言"——被测逻辑写错时,它会失败?
所有 Mock 都用于外部不可控依赖,没有被测逻辑自身被 Mock?
每个异常分支都有对应用例和断言?
抽样做过"改错验红"——故意改错被测代码,相关测试确实会变红?

AI 能一分钟写出测试的样子,但这测试到底兜不兜得住事,目前还得靠人自己动手戳一下。你的仓库里如果正好有一批 AI 生成的全绿测试,今天就改一个运算符试试。