大数跨境

单元测试指标,最容易骗过管理层,到了AI时代,这个问题又被放大了!

单元测试指标,最容易骗过管理层,到了AI时代,这个问题又被放大了! 软件工程3.0时代
2026-10-29
0
导读:我们真正需要的,不是更漂亮的覆盖率数字,而是那些在关键逻辑出错时,真的能够站出来说“不对”的测试
今天中午,在“软件测试与质量”微信群里,有人又聊起单元测试覆盖率的事。

有人提到,曾经见过一个不小的项目,流水线上设了单元测试覆盖率门禁,报表一直是绿的。后来第三方咨询团队进场检查,才发现一个让人哭笑不得的问题:不少单元测试只是调用了被测方法,根本没有断言。代码执行了,测试通过了,至于结果对不对,没人验证。

群里马上有人接话:“我们公司也有覆盖率门禁,不过我从来不看那个数。”另一位说得更直接:“我们压根就没要求。”

我看着这些讨论,觉得这事还真值得认真聊聊。

单元测试覆盖率本来是个好东西,为什么到了项目管理中,有时反而成了自欺欺人的工具?尤其现在有了 AI Coding,测试代码可以批量生成,覆盖率可以迅速提高,这个问题恐怕比以前更值得警惕。

先看一个简单得不能再简单的例子。

假设我们要测试一个退款计算函数:

double refund(double amount, double rate) {    if (amount > 0) {        return amount * rate;    }    return 0;}

有人可能会这样写单元测试:

@Testvoid testRefund() {    refund(100, 0.8);}

测试运行成功,相关代码也执行了。但退款金额究竟应该是多少?80 元还是 100 元?测试没有给出任何答案。

如果再加一句:

assertNotNull(refund(100, 0.8));

看起来终于有断言了。可假如程序错误地返回了 100 元,这个测试照样通过。

真正有意义的测试,至少应该明确验证:输入 100 元、退款比例为 0.8 时,预期退款金额是 80 元。

这个例子很简单,却说明了一个经常被忽略的问题:

代码执行过,不等于代码被有效测试过;测试通过了,也不等于业务逻辑正确。

行覆盖率、分支覆盖率都能告诉我们代码执行到了哪里,却无法判断测试有没有验证正确的业务行为。一个测试即使让某个分支执行了,也可能对这个分支算出来的错误结果视而不见。

当然,覆盖率不是没用。它可以帮助我们发现哪些代码还没有被测试执行到。问题在于,我们常常把它当成了测试质量的证明。

事情到了项目管理层面,就更有意思了。

假设一个团队被要求:单元测试覆盖率必须达到 85%,否则代码不允许合并。

如果时间充裕,工程师可以认真分析业务规则,补充边界值、异常输入和关键参数组合。如果项目马上要交付,覆盖率还差一截,最快的办法是什么?往往是先让那些没有覆盖到的方法跑起来。

前一种做法费脑子,后一种做法见效快,而且马上能在报表上看到结果。

当管理者只考核覆盖率,却不检查测试是否有效时,团队就很容易把精力花在提高数字上,而不是发现缺陷上。这正是古德哈特定律所描述的现象:当一个指标成为目标,它就可能不再是一个好的指标。

覆盖率门禁本身没有错,错的是把覆盖率达标当成了质量达标。

到了 AI Coding 时代,这个问题又被放大了。

以前,补一批单元测试需要工程师花时间编写。现在,AI 可以很快生成大量测试代码,覆盖率也可能迅速上升。但生成了测试,不代表测试就有价值。AI 可能只检查方法能否正常执行,也可能写出一些看似有断言、实际约束很弱的测试;如果过度依赖 Mock,还可能把真正需要验证的业务逻辑隔离在外。

于是,团队可能很快得到一份漂亮的覆盖率报表,却没有获得与之相称的质量保障。

问题不在 AI,而在于我们让 AI 优化什么。

如果目标只是把覆盖率从 60% 提到 85%,AI 可以帮助我们更快地完成任务。如果目标是发现边界条件中的缺陷、验证关键业务规则,就不能只看生成了多少测试,还得检查这些测试究竟验证了什么。

那到底应该看什么?

我的看法是,覆盖率仍然要看,但不能只看覆盖率。对于关键模块,至少还要问三个问题:测试有没有明确的业务断言?边界和异常场景有没有得到验证?代码出现错误时,测试能不能发现?

这里可以引入一种技术:变异测试。

它的思路很直观:故意把代码中的某些运算符或条件改错,再运行测试。如果代码已经被改坏,测试却依然全部通过,就说明测试可能没有真正约束这段逻辑。

变异测试有成本,也不是万能的,但它至少比单纯统计代码执行次数更接近一个关键问题:如果代码错了,我们的测试能不能发现?

比指标本身更重要的,是团队写单元测试的方式。

如果测试总是在代码写完之后才补,目的只是让流水线变绿,那么测试很容易沦为对现有实现的追认。测试驱动开发(TDD)的价值,在于先写测试,明确预期行为,再编写代码让测试通过。它不能保证测试一定正确,却能促使开发者在实现之前先想清楚:什么输入应该得到什么结果,哪些情况必须拒绝,哪些边界不能出错。

在 AI Coding 工作流中,这个顺序同样重要。先分析业务行为、边界和风险,再让 AI 辅助生成测试,而不是简单地让 AI 根据现有代码补齐覆盖率。

说到底,单元测试不是为了证明我们写了多少测试代码,而是为了在代码出错时,尽可能早地发现问题。

所以,如果你正在管理一个研发团队,不妨看一眼自己的覆盖率报表,再问一个问题:

如果把核心业务代码故意改错,这些测试真的会失败吗?

如果答案不确定,那么无论覆盖率是 80%、90% 还是 100%,都还不足以证明这部分代码得到了有效保护。

工具、门禁和报表都是管理质量的手段,但它们不能代替对质量本身的判断。

我们真正需要的,不是更漂亮的覆盖率数字,而是那些在关键逻辑出错时,真的能够站出来说“不对”的测试。

【声明】内容源于网络
0
0
软件工程3.0时代
由于大模型(LLM)正在改变着千行百业,软件工程(SE)更是首当其冲,迎来软件工程3.0新时代:模型驱动研发、模型驱动运维。本公众号将致力于研究SE3.0时代的软件研发新范式、理论与方法,介绍SE3.0时代的工具与实践。
内容 642
粉丝 0
软件工程3.0时代 由于大模型(LLM)正在改变着千行百业,软件工程(SE)更是首当其冲,迎来软件工程3.0新时代:模型驱动研发、模型驱动运维。本公众号将致力于研究SE3.0时代的软件研发新范式、理论与方法,介绍SE3.0时代的工具与实践。
总阅读7.8k
粉丝0
内容642