13、分支覆盖率与路径覆盖率:理解不同覆盖率指标、如何提高覆盖率

说到覆盖率,很多刚入行的朋友容易混淆几个概念。我记得有一次评审代码,一个同事拍着胸脯说「我行覆盖率100%了」,结果我一看,分支覆盖率才60%多。嗯,这就是典型的只盯着一种指标看。

今天咱们就把分支覆盖率和路径覆盖率掰开揉碎了讲清楚。说白了,它们俩就像是你测试的两只眼睛——一只看「有没有走岔路」,另一只看「路走得对不对」。

13.1 分支覆盖率:你到底走了哪条路?

分支覆盖率,英文叫 Branch Coverage。它衡量的是:代码中的每个条件分支(if-else、switch-case、三元运算符等)是否都被执行过。

举个例子,你写了个简单的判断函数:

int check_temperature(int temp) {
    int status;
    if (temp > 30) {
        status = 1;  // 高温
    } else {
        status = 0;  // 正常
    }
    return status;
}

这个函数有两个分支:temp > 30 为真和 temp > 30 为假。如果你只测了 temp=35,那分支覆盖率只有50%。因为 else 分支根本没跑到。

关键点:分支覆盖率关注的是「每个分支是否被执行」,而不是「每行代码是否被执行」。行覆盖率100%不代表分支覆盖率100%。

我在项目中遇到过这样的坑:某个模块行覆盖率95%,但分支覆盖率只有40%。为什么?因为很多 if-else 的 else 分支从来没被触发过。后来线上出了个边界条件的bug,就是那个没测到的 else 分支里的逻辑。

13.2 路径覆盖率:你走完了所有路线吗?

路径覆盖率就更有意思了。它要求你覆盖函数中所有可能的执行路径。注意,是「所有路径的组合」。

看这个例子:

int process_data(int a, int b, int flag) {
    int result = 0;
    if (a > 0) {
        result += 10;
    }
    if (b > 0) {
        result += 20;
    }
    if (flag) {
        result *= 2;
    }
    return result;
}

这里有3个if,每个if有2种情况。理论上路径数量是 2³ = 8 条。但实际上,有些路径可能永远走不到(比如某些条件互斥)。

注意:路径覆盖率有个「组合爆炸」的问题。一个函数如果有10个if,理论上就有 2¹⁰ = 1024 条路径。你不可能全部测完。所以实际项目中,我们通常只测「有意义的路径」。

我个人习惯的做法是:先画出控制流图,然后挑出「典型路径」和「边界路径」。比如上面那个函数,我会测:

  • 全真路径:a>0, b>0, flag=1
  • 全假路径:a≤0, b≤0, flag=0
  • 混合路径:a>0, b≤0, flag=1
  • 边界路径:a=0, b=0, flag=0

这样4条路径就能覆盖大部分场景。你想想看,如果非要测8条,很多路径其实是冗余的。

13.3 两种覆盖率的对比

指标 关注点 优点 缺点
分支覆盖率 每个分支是否被执行 容易理解,工具支持好 无法发现路径组合问题
路径覆盖率 所有执行路径是否被覆盖 能发现组合逻辑bug 路径数量可能爆炸

说白了,分支覆盖率是「基础要求」,路径覆盖率是「进阶要求」。我一般建议:

  • 单元测试阶段:分支覆盖率 ≥ 90%
  • 集成测试阶段:关键模块的路径覆盖率 ≥ 80%
  • 安全关键系统(比如汽车、医疗):路径覆盖率 ≥ 95%

13.4 如何提高覆盖率?

提高覆盖率不是靠「堆测试用例」,而是靠「设计测试用例」。我总结了几条实战经验:

13.4.1 先画控制流图

别急着写测试代码。先把函数的控制流图画出来。每个判断节点、每个分支、每条路径,一目了然。

控制流图示例:process_data 函数 入口 a > 0? result+=10 跳过 b > 0? result+=20 跳过 出口

13.4.2 用等价类划分法

别傻乎乎地每个值都测。把输入分成几个等价类,每个类选一个代表值就行。比如温度判断:

  • 有效等价类:temp=25(正常)、temp=35(高温)
  • 边界等价类:temp=30(刚好等于阈值)
  • 无效等价类:temp=-5(异常值)

13.4.3 边界值分析法

很多bug都出在边界上。我习惯把每个判断条件的「上点、下点、内点」都测一遍。比如 temp > 30

  • 上点:30(刚好不满足)
  • 下点:31(刚好满足)
  • 内点:35(典型值)

小技巧:写测试用例时,先写「正向用例」覆盖主要路径,再写「反向用例」覆盖异常分支。我曾经用这个方法,把一个模块的分支覆盖率从55%提到了92%。

13.4.4 善用工具

别手动算覆盖率。用工具:

  • GCC + gcov:Linux下最常用
  • LCOV:生成可视化报告
  • BullseyeCoverage:商业工具,支持嵌入式

我一般用 gcov 生成报告后,重点关注「未覆盖的分支」。那些红色的行,就是你的测试盲区。

13.5 避坑指南

我曾经踩过的坑:

  • 只盯着行覆盖率,忽略了分支覆盖率。结果线上出了个「else分支没测到」的bug。
  • 路径覆盖率追求100%,写了大量冗余用例。后来发现,有些路径根本不可能走到(比如互斥条件)。
  • 没有考虑「短路求值」。比如 if (a && b),如果 a 为假,b 根本不会执行。分支覆盖率可能漏掉 b 的分支。

嗯,这里要注意:分支覆盖率和路径覆盖率不是「越高越好」,而是「够用就好」。我见过有人为了把路径覆盖率从85%提到90%,多写了30个测试用例,结果发现那5%的路径全是「不可能到达的」。浪费精力。

最后说一句:覆盖率是手段,不是目的。你的目标是「发现bug」,而不是「凑数字」。我见过太多团队,覆盖率报表漂漂亮亮,线上bug一个接一个。为什么?因为测试用例的质量不行,光顾着凑覆盖率了。

所以我的建议是:先保证分支覆盖率到90%,然后针对关键模块做路径覆盖。别贪多,也别偷懒。稳扎稳打,比什么都强。


公众号:蓝海资料掘金营,微信deep3321