第3章:单元测试方法论:白盒测试基础、语句覆盖、分支覆盖、MC/DC覆盖、条件覆盖

好,咱们今天聊聊单元测试里最核心的东西——白盒测试。说实话,很多刚入行的工程师觉得白盒测试就是“看代码写测试”,这话对了一半。白盒测试的精髓,是你要知道代码怎么跑,然后设计用例去验证它。

我个人习惯把白盒测试比作“拆解一台发动机”。你不仅要知道它能不能转,还得知道每个活塞、每个气门是怎么配合的。嗯,咱们今天就把这些“零件”拆开看看。

白盒测试基础:你凭什么相信代码?

白盒测试,说白了就是基于代码内部结构的测试。你看到了if、else、for、while,你就得想:这些分支我测全了吗?

我记得有一次做安全气囊控制器的测试,一个简单的条件判断没覆盖到,结果在某个极端情况下气囊该爆没爆。那次之后,我对白盒测试的敬畏心就上来了。

白盒测试的核心指标就是覆盖率。常见的覆盖率有几种:

  • 语句覆盖:每行代码都执行过
  • 分支覆盖:每个判断的真假分支都走过
  • 条件覆盖:每个原子条件的真假都测过
  • MC/DC覆盖:每个条件能独立影响结果

你想想看,如果连语句都没跑全,你怎么敢说代码没问题?

语句覆盖:最基础,但别小看它

语句覆盖的要求很简单:让每一条可执行语句至少执行一次。听起来容易,对吧?

看个例子:

int calculate(int a, int b) {
    int result = 0;
    if (a > 0) {
        result = a + b;
    }
    return result;
}

如果你只传 calculate(1, 2),那 result = a + b 这行执行了,没问题。但如果你传 calculate(-1, 2),那这行就没执行。所以语句覆盖要求你至少有一个用例让 a > 0 成立。

我的经验: 语句覆盖是最低要求。在ASPICE的评估中,如果语句覆盖都达不到100%,那基本过不了。但注意,语句覆盖100%不代表代码没问题——它只是说每行代码都跑过一遍。

分支覆盖:让每个岔路口都走一遍

分支覆盖比语句覆盖更进一步。它要求每个判断的真假分支都至少执行一次。

还是刚才的例子:

if (a > 0) {
    result = a + b;
} else {
    result = a - b;
}

分支覆盖要求你至少有两个用例:一个让 a > 0 为真,一个让 a > 0 为假。这样两个分支都走到了。

我曾经在一个项目中遇到过这样的坑:一个if-else结构,测试只测了真分支,假分支的逻辑里有个指针没判空,结果上线就崩了。嗯,从那以后我特别强调分支覆盖。

注意: 分支覆盖不等于条件覆盖。分支覆盖看的是整个判断的结果,而条件覆盖看的是每个原子条件。别搞混了。

条件覆盖:拆到最小单元

条件覆盖要求每个原子条件(比如 a > 0b == 1)都取过真和假。

看个复杂点的:

if (a > 0 && b == 1) {
    // do something
}

这里有两个原子条件:a > 0b == 1。条件覆盖要求:

  • a > 0 为真和假都测过
  • b == 1 为真和假都测过

但注意,条件覆盖不要求这两个条件组合起来覆盖所有分支。比如你测了 (a>0为真, b==1为假)(a>0为假, b==1为真),两个条件都取过真假了,但整个if语句可能一次都没进去过(因为 && 要求两个都为真)。

这就是条件覆盖的局限性。所以有了MC/DC。

MC/DC覆盖:汽车行业的硬门槛

MC/DC,全称是 Modified Condition/Decision Coverage。翻译过来就是“修正条件/判定覆盖”。名字挺绕,但核心思想很简单:每个条件都能独立地影响整个判断的结果

为什么汽车行业这么看重MC/DC?因为安全。ISO 26262里明确要求,ASIL C和D等级的功能安全软件,MC/DC覆盖率必须达到100%。

MC/DC的要求有几点:

  1. 每个原子条件都取过真和假
  2. 每个判断都取过真和假
  3. 每个条件都能独立影响判断结果——也就是说,改变这个条件,其他条件不变,结果会变

看个例子:

if (A && B) {
    // do something
}

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

用例 A B 结果 说明
1 进入if
2 B独立影响结果
3 A独立影响结果

你看,用例2和3分别证明了B和A能独立影响结果。这就是MC/DC的精髓。

关键点: MC/DC不是要求所有条件组合都测到,而是要求每个条件都能“单独说了算”。这大大减少了测试用例数量,同时保证了测试的充分性。

实际项目中的选择

在ASPICE流程中,不同安全等级对覆盖率的要求不同:

安全等级 推荐覆盖率 说明
QM 语句覆盖 100% 基本要求
ASIL A 分支覆盖 100% 需要验证所有分支
ASIL B 条件覆盖 100% 需要验证每个条件
ASIL C/D MC/DC 100% 最高要求,安全关键

我个人建议,即使项目要求不高,也尽量往MC/DC靠。为什么?因为MC/DC能发现那些“隐藏很深”的逻辑错误。我曾经在一个制动控制模块中,用MC/DC发现了一个条件短路导致的逻辑漏洞——那个bug在语句覆盖和分支覆盖下都测不出来。

避坑指南: 我曾经见过一个团队,为了追求MC/DC覆盖率,写了大量冗余的测试用例。其实没必要。MC/DC的用例数量理论上限是 2N(N是条件数量),但实际中往往只需要 N+1 个用例。别盲目堆用例,要理解每个用例的目的。

好了,这一章的内容就到这儿。白盒测试的几种覆盖方法,从语句覆盖到MC/DC,一层比一层严格。你想想看,你的项目现在做到哪一层了?下一章咱们聊聊集成测试,看看怎么把这些单元拼起来验证。