上周在徐庄软件园楼下的咖啡店,碰着个做企业SaaS的后端组长,刚灌了半杯冰美式就开始吐槽。老板上周发了话,全组单元测试覆盖率要从32%拉到80%,完不成不许申请下周的调休。
他们组熬了整整五个晚上,每天改测试到两点,最后覆盖率工具一跑,81.7%。结果上线第三天,支付回调的逻辑炸了——用户付了钱,订单状态还是待支付,赔了快两万块的优惠券。
你写的测试用例,可能只是给工具看的
我当时拉过他们的测试代码扫了两眼,差点把嘴里的美式喷出来。
有个订单状态流转的核心方法,测试用例就两行。
test void testChangeStatus(){
OrderService service = new OrderService();
service.changeStatus(new Order());
}
连返回值都没断言,连方法里的if分支走的哪条都不知道。
没用。
覆盖率工具只看你的代码有没有被执行到,根本不管你有没有验证逻辑对不对。他们为了凑行数,把所有外部依赖全mock成了固定返回值,连真实的数据库交互、第三方接口回调的异常逻辑碰都没碰。

很多人对覆盖率的理解停留在”我跑过这段代码就算覆盖了”,根本不知道行覆盖、分支覆盖、路径覆盖的区别。比如一个带两个if判断的方法,你只测了正常流程,行覆盖能到100%,但分支覆盖可能连50%都不到。那些隐藏的异常分支——比如参数为空、网络超时、权限不足,全在覆盖率统计的盲区里藏着,直到线上炸了才有人发现。
先搞清楚你要覆盖的到底是什么
说实话,一上来就要求全项目覆盖率到80%的老板,大概率是没见过真实的业务代码。
谁还没点历史包袱?三年前的老工具类、没人维护的边缘脚本、写了就从来没被调用过的废弃方法,这些东西你就算花一个月把覆盖率拉满,对线上稳定性有半毛钱帮助?

我给那个组长提的第一个建议,就是先把所有代码按业务价值分个类。
核心业务模块——支付、订单流转、用户权限校验这些直接影响收入和用户数据的,必须做到分支覆盖100%,每一个异常分支都要有用例兜底。
普通业务模块——比如用户中心的个人信息修改、商品列表查询这些出了问题影响不大的,行覆盖到70%就够。
边缘工具类——比如字符串处理、日期格式化的通用工具,行覆盖到50%就行,没必要死磕那些永远走不到的异常分支。
不过话说回来,要是你们团队连哪些是核心模块都分不清,那这事根本推不动。我见过最离谱的团队,为了凑最后2%的全量覆盖率,花了三天时间去测一个永远不会触发的数据库连接超时分支,最后还因为写测试改坏了工具类的逻辑,导致全平台的日期格式化出错,停服了俩小时。
真的,覆盖率从来不是越高越好。你为了覆盖一个一年都触发不了一次的异常分支花的时间,不如多写两个核心流程的集成测试,性价比高太多了。
真能落地的提升方法,根本不靠加班
那个组长后来按我说的方法改了策略,没加班,三天就把核心模块的分支覆盖率拉到了100%,线上相关的bug量直接降了40%。
他们是怎么做的?
先改考核规则。不再考核全量覆盖率,只盯每次代码改动的增量覆盖率。你这次提PR改了哪个方法,那这个方法的分支覆盖率必须到100%,不然不许合并。之前的老代码覆盖率低没关系,你什么时候改到这段代码,什么时候顺手把测试补了就行。这个规则一改,没人会为了凑全量覆盖率去翻三年前的老代码,也不会出现新人进团队因为历史代码覆盖率低合不了PR的尴尬情况。
别集中突击补测试。很多团队的操作是,平时写业务代码根本不写测试,等到老板要考核覆盖率了,才集中花一两周补测试,补出来的全是刚才那种没用的凑数用例。正确的做法是,你写完一个方法的正常逻辑,顺手就把对应的测试写了——比如你刚写完手机号重复校验的逻辑,顺手就把”输入已注册手机号返回错误”的用例写了,前后也就花五分钟,比你两周后回头补测试效率高十倍,还不会出现漏写逻辑的情况。
也别迷信工具的自动生成。现在很多IDE能自动生成测试用例,生成出来的全是那种传默认参数调用方法的垃圾用例,连断言都没有,除了凑行数啥用没有。真要写好测试用例,你得自己想清楚,这个方法在什么情况下会出问题,什么参数会触发异常分支,这些东西自动工具是帮不了你的。
上周那个组长给我发消息,说他们现在已经没人再为了凑覆盖率加班了,每次改代码顺手补测试,核心模块的覆盖率稳在100%,线上的支付bug再也没出过。
真的,别再为了给覆盖率工具打工了。你写测试的目的是为了少改bug,不是为了给老板看那个数字好看。