3、覆盖率类型详解:语句覆盖率、分支覆盖率、条件覆盖率、MC/DC覆盖率

好,咱们直接切入正题。覆盖率这东西,说白了就是一把尺子,量一量你的测试到底测得多深。很多新手觉得跑一遍模型,看到覆盖率100%就万事大吉了。嗯,我当年也这么天真过。直到有一次,一个看似全覆盖的模型,在实车上出了严重故障——从那以后,我才真正开始琢磨每种覆盖率到底在测什么。

今天我就把这四种最常见的覆盖率掰开揉碎了讲。你想想看,它们就像四层筛子,一层比一层细。

3.1 语句覆盖率(Statement Coverage)

这是最基础、最入门的一种。定义很简单:你的测试用例执行了多少比例的代码语句?

举个例子,看下面这段Simulink里的MATLAB Function代码:

function y = check_speed(speed)
    if speed > 120
        y = 1;  % 超速
    else
        y = 0;  % 正常
    end
    y = y + 1;  % 偏移处理
end

如果你只给一个输入 speed = 100,那它只走了else分支。语句覆盖率是多少?4条语句里执行了3条(if判断、else赋值、偏移处理),覆盖率75%。

核心要点:语句覆盖率只关心「这行代码跑没跑过」,不关心「跑得对不对」。

我个人习惯把语句覆盖率当作「底线检查」。如果连这个都做不到100%,那后面的覆盖率分析基本不用谈。但注意,100%语句覆盖率不代表没有bug。我在项目中遇到过,语句全覆盖了,但条件组合错了,照样出问题。

小技巧:做MIL测试时,先跑一遍语句覆盖率,快速定位那些「死代码」——就是永远执行不到的语句。这些往往是建模时的遗留垃圾,该删就删。

3.2 分支覆盖率(Branch Coverage)

分支覆盖率比语句覆盖率更进一步。它不光看语句执行没执行,还要看每个判断点的「真」和「假」两个分支是否都走过。

还是上面那个例子:

if speed > 120
    y = 1;
else
    y = 0;
end

分支覆盖率要求:

  • 分支「真」(speed > 120 成立)至少执行一次
  • 分支「假」(speed > 120 不成立)至少执行一次

所以你需要两个测试用例:speed = 150speed = 80。这样分支覆盖率就是100%。

注意:分支覆盖率和语句覆盖率不是包含关系。有时候分支全覆盖了,语句覆盖率可能还不是100%。比如某个分支里有多条语句,但你的测试只覆盖了分支入口,没覆盖分支内部的所有语句——这种情况在复杂模型中很常见。

我曾经在一个项目中,分支覆盖率做到了100%,但语句覆盖率只有85%。排查后发现,某个分支里有一段代码被前面的return语句截断了。嗯,这就是典型的「死代码」问题。

3.3 条件覆盖率(Condition Coverage)

好,现在咱们上点难度。条件覆盖率关注的是每个「原子条件」的真假值是否都被测试过。

什么叫原子条件?就是不能再拆分的布尔表达式。比如:

if (A && B) || C
    ...
end

这里有三个原子条件:A、B、C。条件覆盖率要求:

  • A 至少取一次 true 和一次 false
  • B 至少取一次 true 和一次 false
  • C 至少取一次 true 和一次 false

注意,它不关心这些条件的组合结果。你想想看,A=true、B=false、C=false 这个组合,虽然整体判断为false,但A的true已经被覆盖了,这就够了。

关键区别:分支覆盖率看的是「整个判断的结果」,条件覆盖率看的是「每个条件的取值」。

我个人觉得,条件覆盖率是很多工程师容易忽略的点。大家往往只盯着分支覆盖率看,觉得分支都走了就OK了。但实际项目中,条件覆盖率的缺失往往意味着某些边界情况没测到。

3.4 MC/DC覆盖率(Modified Condition/Decision Coverage)

终于到了这个「大魔王」。MC/DC是航空、汽车功能安全领域(比如ISO 26262的ASIL D)强制要求的覆盖率。它到底在测什么?

MC/DC要求:

  1. 每个入口点和出口点至少执行一次(语句覆盖)
  2. 每个判断的真假分支至少执行一次(分支覆盖)
  3. 每个原子条件都能独立地影响判断结果

第三条是精髓。什么叫「独立地影响」?就是说,改变这个条件的取值,其他条件保持不变,判断结果必须跟着变。

看个经典例子:

if (A && B)
    ...
end

要满足MC/DC,你需要两个测试用例:

测试用例 A B 结果 说明
TC1 true true true 基础组合
TC2 false true false A独立影响结果
TC3 true false false B独立影响结果

你看,TC2和TC1对比,B不变,A从true变false,结果从true变false——A独立影响了结果。TC3同理证明了B的独立性。

避坑指南:我曾经在一个项目中,MC/DC覆盖率死活上不去。排查了半天,发现是因为某个条件在逻辑上被短路了。比如 A && B,如果A为false,B根本不会被评估。这种情况下,B的「独立影响」很难证明。解决方案?要么重构逻辑,要么增加额外的测试点。

3.5 四种覆盖率的对比与选择

说了这么多,咱们来个总结对比:

覆盖率类型 关注点 测试用例数量 适用场景
语句覆盖率 每行代码是否执行 快速检查、死代码检测
分支覆盖率 每个判断分支是否走过 中等 常规功能测试
条件覆盖率 每个原子条件的真假 较多 边界条件验证
MC/DC覆盖率 每个条件独立影响结果 最多 功能安全(ASIL C/D)

我个人建议的测试策略是这样的:

  • 项目初期:先跑语句覆盖率,快速清理死代码
  • 功能测试阶段:用分支覆盖率做主要衡量标准
  • 安全关键功能:必须上MC/DC,别偷懒
  • 条件覆盖率:作为MC/DC的补充,特别是有复杂布尔表达式时

最后提醒一句:覆盖率只是手段,不是目的。我见过有人为了凑MC/DC的100%,写了大量无意义的测试用例,结果真正的逻辑漏洞反而没发现。记住,覆盖率是告诉你「哪里没测到」,而不是「测够了」。

好了,这四种覆盖率就讲到这里。下一章咱们聊聊怎么在实际项目中把这些覆盖率工具用起来,包括怎么配置工具链、怎么分析报告。到时候我会分享一些我踩过的坑,保证实用。