sonar覆盖率怎么统计的_浅谈单元测试代码覆盖率

文章探讨了代码覆盖率作为衡量单元测试质量的指标,从语句覆盖、判定覆盖、条件覆盖到路径覆盖,指出其优缺点,并强调覆盖率不能完全代表代码质量,测试人员应设计全面的测试用例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

c32f8698acf3a562d66b2669d9d3d802.png

代码覆盖率常被用来作为衡量单元测试好坏的指标,于是测试人员费尽心思设计案例覆盖代码。用代码覆盖率来衡量,有利也有有弊。本文我们就代码覆盖率展开讨论。

1. 语句覆盖

语句覆盖又称行覆盖,段覆盖,基本块覆盖,这是最常用也是最常见的一种覆盖方式,就是度量被测代码中每个可执行语句是否被执行到了。可执行语句不包括头文件声明、代码注释、空行等等。只统计可执行的代码被执行了多少行。需要注意的是,单独一行的花括号{} 也常常被统计进去。

语句覆盖只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。因此测试效果不明显,很难发现代码中的问题。

这里举个栗子,我们看以下被测试代码:

int foo(int a, int b){ return a / b;}

假如我们的测试人员编写如下测试用例:

TeseCase: a = 10, b = 5

Hmm,你可以很自豪地说代码覆盖率达到了100%,并且所有测试用例都Pass了。然鹅我们却没有发现最简单的Bug(当b=0时,会抛出一个除零异常)。

因此,只用语句覆盖率很容易达到所谓的覆盖率,但是并不能保证代码质量。这也反映了几个问题:

  1. 只使用语句覆盖率来衡量代码质量有问题。
  2. 测试的目标是保证代码质量,单一的语句覆盖很难做到。
  3. 是否应该采用更好的测试方法来保证代码质量?

2. 判定覆盖

判定覆盖又称分支覆盖,所有边界覆盖,基本路径覆盖,判定路径覆盖。它度量程序中每一个判定的分支是否都被测试到了。判定覆盖非常容易和下面说到的条件覆盖混淆。因此我们直接和条件覆盖一起来对比,就明白两者是怎么回事了。

3. 条件覆盖

它度量判定中的每个子表达式结果true和false是否被测试到了。为了说明判定覆盖和条件覆盖的区别,我们来举一个例子,假如我们的被测代码如下:

int foo(int a, int b){ if (a < 10 || b < 10) // 判定 { return 0; // 分支一 } else { return 1; // 分支二 }}

设计判定覆盖案例时,我们只需要考虑判定结果为true和false两种情况,因此,我们设计如下的案例就能达到判定覆盖率100%:

TestCaes1: a = 5, b = 任意数字 //覆盖了分支一TestCaes2: a = 15, b = 15 //覆盖了分支二

设计条件覆盖案例时,我们需要考虑判定中的每个条件表达式结果,为了覆盖率达到100%,我们设计了如下的案例:

TestCase3: a = 5, b = 5 //true, trueTestCase4: a = 15, b = 15 //false, false

通过上面的例子,我们应该很清楚了判定覆盖和条件覆盖的区别。需要特别注意的是:条件覆盖不是将判定中的每个条件表达式的结果进行排列组合,而是只要每个条件表达式的结果true和false测试到了就OK了。因此,我们可以这样推论:完全的条件覆盖并不能保证完全的判定覆盖。比如上面的例子,假如我设计的案例为:

TestCase5: a = 5, b = 15 //true, false 分支一TestCase6: a = 15, b = 5 //false, true 分支一

我们看到,虽然我们完整的做到了条件覆盖,但是我们却没有做到完整的判定覆盖,我们只覆盖了分支一。上面的例子也可以看出,这两种覆盖方式看起来似乎都不咋滴。我们接下来看看第四种覆盖方式。

4. 路径覆盖

路径覆盖又称断言覆盖。它度量了是否函数的每一个分支都被执行了。所有可能的分支都要执行一遍,有多个分支嵌套时,需要对多个分支进行排列组合,因此,测试路径随着分支的数量指数级别增加。

比如下面的测试代码中有两个判定分支:

int foo(int a, int b){ int nReturn = 0; if (a < 10) {// 分支一 nReturn += 1; } if (b < 10) {// 分支二 nReturn += 10; } return nReturn;}

对上面的代码,我们分别针对我们前三种覆盖方式来设计测试案例:

a. 语句覆盖

TestCase a = 5, b = 5 nReturn = 11 //语句覆盖率100%

b. 判定覆盖

TestCase1 a = 5, b = 5 nReturn = 11TestCase2 a = 15, b = 15 nReturn = 0//判定覆盖率100% 

c. 条件覆盖

TestCase1 a = 5, b = 15 nReturn = 1TestCase2 a = 15, b = 5 nReturn = 10//条件覆盖率100% 

我们看到,上面三种覆盖率结果看起来都达到了100%!但是上面被测代码中,nReturn的结果一共有四种可能的返回值:0,1,10,11,而我们上面的针对每种覆盖率设计的测试案例只覆盖了部分返回值,因此,可以说使用上面任一覆盖方式,虽然覆盖率达到了100%,但是并没有测试完全。

接下来我们来看看针对路径覆盖设计出来的测试案例:

TestCase1 a = 5, b = 5 nReturn = 0TestCase2 a = 15, b = 5 nReturn = 1TestCase3 a = 5, b = 15 nReturn = 10TestCase4 a = 15, b = 15 nReturn = 11//路径覆盖率100% 

路径覆盖将所有可能的返回值都测试到了。这也正是它被很多人认为是“最强的覆盖”的原因了。

还有一些其他的覆盖方式,如:循环覆盖(LoopCoverage),它度量是否对循环体执行了零次,一次和多余一次循环。剩下一些其他覆盖方式就不介绍了。

总结

1. 覆盖率只能代表测试过代码,不能代表是否全方位测试;

2. 不要过于相信覆盖率数据,它并不能完全衡量代码质量;

3. 路径覆盖率 > 判定覆盖 > 语句覆盖

4. 测试人员不能盲目追求代码覆盖率,而应该设计更全面的测试用例;

### 如何在 Sonar 中实现增量代码的单元测试覆盖率检测 为了实现在 SonarQube 中检查增量代码的单元测试覆盖率,需要配置合适的工具链并遵循特定的工作流程。以下是具体方法: #### 配置环境与工具 SonarQube 是一个强大的代码质量管理平台[^1],而 JaCoCo 可以记录代码覆盖率完全依赖于对原始代码的插桩,在 CI 实践中已经广泛应用于单元测试代码覆盖率统计[^3]。 #### 设置 Jenkins 和 SonarQube 的集成 当代码被推送至代码库时,Jenkins 将触发构建过程,并利用 JaCoCo 统计单元测试代码覆盖率的数据,随后这些数据会被上传到 SonarQube 平台进行展示和分析。 #### 启用增量分析模式 要在 SonarQube 上启用增量代码的单元测试覆盖率检测,需设置 `sonar.coverage.exclusions` 参数排除不必要的文件路径,并确保只针对新提交或修改过的代码计算覆盖率指标。这可以通过调整项目的属性文件完成,例如 `.properties` 文件中的如下配置项: ```properties # 排除某些目录下的文件不参与覆盖率计算 sonar.coverage.exclusions=src/test/**/*, src/main/java/com/example/old/** ``` 此外,还需要开启分支保护规则以及 PR (Pull Request) 分析功能,以便每次创建新的 Pull Request 时自动启动该特性,从而专注于最近更改的部分。 #### 使用 Opencover 或其他替代方案生成报告 如果遇到无法获取正确覆盖率的情况,则可能是因为缺少必要的集成步骤。此时可考虑引入像 OpenCover 这样的第三方工具来辅助生成更精确的单元测试覆盖报告文档[^4]。 通过上述措施可以在 SonarQube 中有效地监控增量代码的变化及其对应的单元测试覆盖率情况,进而帮助团队持续改进软件质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值