让AI写单元测试,最迷惑的场景不是测试挂了,而是全绿。一个订单满100减10%的函数,在金额正好是100时返回了100而不是90,可两套测试跑下来一个都没报错。
问题出在期望值从哪来。当智能体先写完代码、再回头补测试,它写的测试描述的是自己刚写的那份代码,于是连bug一起被"验证通过"了。
素材里给了两个典型写法,都用TypeScript和Vitest。被测试的函数长这样:订单满100打九折,但判断条件写成了大于100,而不是大于等于。
第一种:把被测函数mock掉。测试里直接让applyDiscount返回90,然后断言它等于90。这个断言检查的是mock,真实代码一行都没跑过。给支付服务这类外部依赖做mock是正当用法,但mock掉自己正要验证的函数,等于自己给自己发合格证。
第二种:用代码自己的公式算期望值。测试里照着实现写了一遍total > 100 ? total * 0.9 : total,连边界bug一起抄了进去。代码错在哪,期望值就错在哪,两边永远对得上。
这两种写法的共同点是:期望值来自智能体自己写的代码。只要来源是代码,测试就可能重复代码的错误,而不是抓住它。
有研究量化过这个现象。一项2026年7月的研究里,11个模型、13种模型与模式组合,为318个Java方法写了测试,这些方法背后对应233个真实bug。平均下来,3.84%的测试是从有bug的代码写出来的,它们在bug上通过、在修复后失败。而从修复后的代码写出来的测试,这个比例是0.46%(Zhao、Zhou与Cohen)。
那多写点测试能不能解决?另一项2026年2月首次发布的研究,在500个SWE-bench Verified任务上做了对照。逼智能体多写测试,解出的任务数几乎没变:GPT-5.2两种情况下都是359个,Gemini 3 Pro Preview从371变成366。反过来告诉Kimi K2 Thinking和DeepSeek v3.2 Reasoner别写测试,成绩从317掉到304、从300掉到291。这些变化在统计上都不显著。
成本倒是跟着模型走。GPT-5.2多写了测试,API调用和token都更多,解出的任务却没变多;被要求跳过测试的两个模型,用量则下降了。这项研究只覆盖一个基准和一个智能体脚手架mini-SWE-agent,也没有测试TDD或下面这套流程。
可行的做法是把节奏切小:先定义"完成"是什么,而且这个定义要能被证伪;期望值必须取自智能体没写过的东西——工单、规格说明、真实案例;先写一个失败的测试(红),它必须是因为行为缺失而失败;再写刚好够让它通过的代码(绿);然后进入下一片,评审放在绿之后。
回到折扣的例子,测试的期望值直接来自工单:"金额正好100的订单付90。"拿它去跑智能体的代码,会红着报出expected 100 to be 90。把>改成>=,它转绿。那个mock测试依然通过,因为它从没跑过真实代码;而抄公式的测试这时反而失败了——它一直在保护那个bug。
这套流程有人做成了工具。Matt Pocock的/tdd技能就是它的一个版本,一次只切一片,一次性写完全部测试在那里属于反模式,重构归评审阶段。Kent Beck的Canon TDD则允许在每个循环内、转绿之后重构,这是个人选择。Lauren Tan(poteto)的pstack把第一步做进了/figure-it-out技能:在开始运行前,先让智能体说清楚"完成"意味着什么,并且这个说法要能被证伪。
最后要认清一件事:再好的单元测试也只是检查表里的一行。全绿不等于没问题,先看每个期望值的出处,比数绿点有用得多。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.