「顺便把测试也写了」大概是 AI 编程助手使用频率最高的指令之一。一次性生成 30 个测试、全绿、覆盖率蹭蹭上涨——很爽。但用了一个季度之后我的结论是:AI 解决了测试的数量问题,同时把质量问题完整地留给了工程师。全绿不等于测了东西。以下是我验收 AI 生成测试的五条标准,不达标一律打回。

一、断言必须独立于实现

最常见也最隐蔽的问题:测试读起来像「照着实现抄了一遍」。实现怎么写,断言就怎么期望——这种测试永远全绿,也永远测不出回归,因为它只是实现的镜像。

对策是换输入源:让 AI 基于需求描述、接口文档或 issue 写测试,而不是把被测代码喂给它再「补个测试」。断言里的期望值应该来自业务规则,而不是来自代码运行结果。

二、覆盖率是下限,不是目标

行覆盖率 90% 但全是 happy path,等于没测。覆盖率只能证明「这些代码被执行过」,不能证明「执行结果被认真校验过」。我看测试质量的第一个动作是数一下:有断言价值的测试占比多少——那些只调用不断言、或只断言「不抛异常」的,直接删。

三、边界与异常路径要点名要

AI 默认爱写正常路径,边界要么不写要么浅尝辄止。所以 prompt 里我会直接列出边界清单:

请为 CalculateDiscount 编写单元测试,必须覆盖:
- 空订单 / null 参数
- 数量为 0 与负数
- 恰好达到折扣门槛的边界值(如满 100 减 10,就测 99.99 / 100 / 100.01)
- 并发取消场景下 CancellationToken 已触发的行为

点名比暗示有效得多。

四、测试必须是确定性的

AI 特别爱写出依赖当前日期、依赖环境的测试——比如让被测代码算「到年底还剩几天」,然后用 DateTime.Now 去断言。这种测试平时全绿,12 月 31 日 23:59 全红。验收时重点检查:

  • 时间是否走注入的 TimeProvider / FakeTimeProvider
  • 随机数种子是否固定;
  • 文件、网络、数据库是否全部隔离(内存实现或测试容器);
  • 本地重复跑 10 次是否结果一致。

五、用变异测试交叉验证

前三条都是人肉审查,最后这条让机器来:「测试能不能抓住真 bug」可以用变异测试直接回答。Stryker.NET 会对被测代码做细微变异(改条件、改返回值),统计测试能否「杀死」这些变异体:

dotnet tool install -g dotnet-stryker
dotnet stryker --project OrderService.Tests.csproj

变异分数低,说明测试看似存在、实则没牙。我一般只对核心业务模块跑变异测试——全量跑太慢,但核心模块的分数必须过线。

写在最后

有人把「AI 测试不可信」当成拒绝 AI 写测试的理由,我的看法恰好相反:正因为生成成本趋近于零,验收标准才成为唯一有价值的稀缺品。这五条标准沉淀成 checklist 之后,团队里 AI 测试的返工率降了一大半。

AI 让「写测试」不再稀缺,「判断测试好不好」成了工程师的新职责。这是坏事吗?我倒觉得是把测试这件事还给了真正懂业务的人。