2、覆盖率度量体系:行覆盖率、分支覆盖率、函数覆盖率、路径覆盖率、条件覆盖率的定义与区别
说实话,我刚入行那会儿,对覆盖率也是一头雾水。
领导说「覆盖率要达标」,我就拼命写用例。结果呢?行覆盖率冲到90%,线上还是出bug。后来我才明白——不同覆盖率指标,看的根本不是同一件事。
今天咱们就把这五个指标掰开揉碎,讲清楚。
2.1 行覆盖率(Line Coverage / Statement Coverage)
这是最基础的指标。说白了,就是「代码中每一行可执行语句,是否至少被执行过一次」。
行覆盖率 = (被执行过的行数 / 总可执行行数) × 100%
举个例子:
def divide(a, b):
if b == 0: # 第1行
return None # 第2行
return a / b # 第3行
如果你只测了 divide(10, 2),那第1行和第3行被执行了,第2行没跑过。
行覆盖率 = 2/3 ≈ 66.7%。
我个人的习惯是:行覆盖率作为「底线指标」。它告诉你代码有没有被「碰过」,但碰没碰对,它不管。
我曾经见过一个项目,行覆盖率95%,但核心的异常处理分支全没测到。为什么?因为行覆盖率只看「行有没有执行」,不看「分支有没有走全」。所以别迷信行覆盖率。
2.2 分支覆盖率(Branch Coverage)
分支覆盖率比行覆盖率更进一步。它关心的是:每个判断语句的真假分支,是否都走过。
还是刚才的例子:
if b == 0: # 这是一个分支点
return None # 真分支
return a / b # 假分支
如果你只测 divide(10, 2),只走了假分支。分支覆盖率 = 1/2 = 50%。
只有当你再测一个 divide(10, 0),两个分支都走了,覆盖率才到100%。
我在项目中遇到过一个问题:分支覆盖率高,不代表逻辑正确。比如你把 if b == 0 写成了 if b != 0,两个分支照样都能走到,但逻辑反了。所以分支覆盖率是「结构覆盖」,不是「语义覆盖」。
分支覆盖率通常比行覆盖率更难达标。我建议团队把分支覆盖率作为「核心指标」,至少要求80%以上。
2.3 函数覆盖率(Function Coverage / Method Coverage)
这个最简单。它问的是:代码里每个函数(或方法),是否至少被调用过一次。
比如你有一个工具类,里面有10个函数。测试用例只调用了其中7个,那函数覆盖率就是70%。
你想想看,这个指标有什么用?
嗯,它特别适合用来发现「死代码」——那些写了但没人调用的函数。我接手过一个老项目,函数覆盖率只有40%,一查,好多函数是几年前写的,早就没人用了。
函数覆盖率应该作为「快速检查指标」。每次跑完测试,先看一眼函数覆盖率。如果低于80%,说明有大量代码根本没被测试触及,赶紧补用例。
2.4 路径覆盖率(Path Coverage)
这个就有点「理想化」了。路径覆盖率要求:程序中所有可能的执行路径,都要被覆盖到。
什么叫路径?就是代码从入口到出口,所有分支组合形成的一条完整路线。
看个例子:
def process(a, b, c):
if a > 0: # 分支1
x = 1
else:
x = 0
if b > 0: # 分支2
y = 1
else:
y = 0
if c > 0: # 分支3
z = 1
else:
z = 0
return x + y + z
这里有3个if,每个if有2个分支。理论上,路径数量 = 2³ = 8条。
但实际项目中,路径数量会爆炸。一个函数里如果有10个if,路径就是2¹⁰ = 1024条。如果有循环?那路径数就是天文数字。
所以路径覆盖率在实际工程中几乎不可能做到100%。
我个人只在两种场景下关注路径覆盖率:
- 核心算法模块:比如加密、排序、状态机,路径必须全覆盖
- 安全关键代码:比如支付、权限校验,漏一条路径可能就是漏洞
别追求全路径覆盖。我一般用「分支覆盖 + 关键路径覆盖」的组合策略。先保证分支全覆盖,再针对核心逻辑补充几条关键路径的用例。
2.5 条件覆盖率(Condition Coverage)
这个指标最容易和分支覆盖率搞混。我来帮你理清楚。
分支覆盖率看的是「整个判断表达式的结果」——真还是假。
条件覆盖率看的是「判断表达式中的每个子条件」——每个子条件是否都取过真和假。
举个例子:
if (a > 0 and b > 0): # 这里有两个子条件:a>0 和 b>0
do_something()
分支覆盖率:只要 (a>0 and b>0) 这个整体取过真和假,就算100%。
条件覆盖率:要求 a>0 取过真和假,b>0 也取过真和假。
你看,区别很明显吧?
我遇到过的一个真实案例:
- 测试用例1:a=1, b=1 → 整体为真,a>0为真,b>0为真
- 测试用例2:a=0, b=0 → 整体为假,a>0为假,b>0为假
分支覆盖率:100%(真和假都走了)
条件覆盖率:50%(a>0只走了真和假?不对,a>0走了真和假,b>0也走了真和假。等等,这里其实两个子条件都覆盖了真和假,所以条件覆盖率是100%。)
嗯,刚才那个例子其实条件覆盖率也达标了。换个例子:
if (a > 0 or b > 0):
do_something()
- 测试用例1:a=1, b=0 → 整体为真,a>0为真,b>0为假
- 测试用例2:a=0, b=0 → 整体为假,a>0为假,b>0为假
分支覆盖率:100%(真和假都走了)
条件覆盖率:75%(a>0:真和假都覆盖了;b>0:只覆盖了假,没覆盖真。所以3/4=75%)
明白了吧?条件覆盖率更细粒度,能发现分支覆盖率漏掉的问题。
条件覆盖率高,不代表分支覆盖率高。反过来也一样。我建议团队同时关注这两个指标,不要二选一。
2.6 五种覆盖率的对比总结
说了这么多,咱们用一张表来收尾:
| 覆盖率类型 | 关注点 | 强度 | 工程可行性 | 我推荐的达标线 |
|---|---|---|---|---|
| 行覆盖率 | 每行代码是否执行 | 低 | 容易 | ≥ 90% |
| 函数覆盖率 | 每个函数是否调用 | 低 | 容易 | ≥ 80% |
| 分支覆盖率 | 每个分支是否走全 | 中 | 中等 | ≥ 80% |
| 条件覆盖率 | 每个子条件是否取遍真/假 | 中高 | 较难 | ≥ 70% |
| 路径覆盖率 | 所有路径是否走全 | 高 | 极难 | 核心模块 ≥ 90% |
最后,我用一张图来展示它们之间的关系:
从这张图能看出来:越往外,覆盖要求越严格,实现难度也越大。函数覆盖率是最容易达标的,路径覆盖率是最难的。
实际工作中,我建议这样组合使用:
- 日常开发:盯住行覆盖率 + 分支覆盖率
- 代码评审:检查条件覆盖率是否达标
- 核心模块:额外要求路径覆盖率
- 函数覆盖率:作为「有没有漏测函数」的快速检查
记住一句话:覆盖率是手段,不是目的。别为了凑数字而写无效用例。我见过有人为了提升行覆盖率,把断言写在注释里——这种自欺欺人的做法,最终坑的是自己。
公众号:蓝海资料掘金营,微信deep3321