详解测试覆盖率

测试覆盖率(test coverage)是衡量软件测试完整性的一个重要指标。掌握测试覆盖率数据,有利于客观认识软件质量,正确了解测试状态,有效改进测试工作。

当然,要发挥这些作用,前提是我们掌握了真实的测试覆盖率数据。通常这并不是一件很直接的事情。那么,如何度量测试覆盖率呢?

在度量测试覆盖率之前,我们需要明确测试覆盖率的定义。毕竟,不同的定义会产生完全不同的覆盖率数据。这里,我基于个人认知和经验,总结三种最常见的测试覆盖率定义及度量方法。

最著名的测试覆盖率就是代码覆盖率。这是一种面向软件开发和实现的定义。它关注的是在执行测试用例时,有哪些软件代码被执行到了,有哪些软件代码没有被执行到。被执行的代码数量与代码总数量之间的比值,就是代码覆盖率

这里,根据代码粒度的不同,代码覆盖率可以进一步分为源文件覆盖率,类覆盖率,函数覆盖率,分支覆盖率,语句覆盖率等。它们形式各异,但本质是相同的。

如何度量代码覆盖率呢?一般可以通过第三方工具完成。不同编程语言,有不同的工具。例如Java语言有Jacoco,Go语言有GoCov,Python语言有Coverage.py。

这些度量工具有个特点,那就是它们一般只适用于白盒测试,尤其是单元测试。对于黑盒测试(例如功能测试/系统测试)来说,度量它们的代码覆盖率则相对困难多了。

主流编程语言一般都有现成的单元测试工具,拿过来稍作配置即可使用。但是,如果想更进一步去了解这些工具背后的实现原理,就需要花费一些功夫了。

以Python覆盖率工具Coverage.py为例,它包括执行,分析和生成报告三大模块。最核心的执行模块依赖Python内置的trace函数。这是一个由Python解释器提供的,当每一行Python代码被执行时所激活的函数。

基于trace函数,我们可以得到每一行被执行的代码所在的文件和行数。然后,结合软件源代码,我们就可以分析出测试的代码覆盖情况,最后生成覆盖报告。

对于黑盒测试,例如功能测试/集成测试/系统测试等来说,测试用例通常是基于软件需求而不是软件实现所设计的。因此,度量这类测试完整性的手段一般是需求覆盖率,即测试所覆盖的需求数量与总需求数量的比值

需求粒度的不同,需求覆盖率的具体表现也有不同。例如,系统测试针对的是比较粗的需求,而功能测试针对的是比较细的需求。当然,它们的本质是一致的。

如何度量需求覆盖率呢?通常没有现成的工具可以使用,而需要依赖人工计算,尤其是需要依赖人工去标记每个测试用例和需求之间的映射关系

对于黑盒测试来说,由于测试是基于用户场景而不是软件实现设计的。因此,这个时候去度量软件代码覆盖率,其意义并不显著,至少是不如需求覆盖率的。

代码覆盖率和需求覆盖率有一个共同点,即它们都是面向测试过程的指标。因此有个不足之处,即覆盖率数据可能无法完全反映真实的测试状态和水平

对于代码覆盖率来说,广为诟病的一点就是100%的代码覆盖率并不能说明代码就被完全覆盖没有遗漏了。因为代码的执行顺序和函数的参数值,都可能是千变万化的。一种情况被覆盖到,不代表所有情况被覆盖到。

对于需求覆盖率来说,100%的覆盖率也不能说“万事大吉”。因为需求可能有遗漏或存在缺陷,测试用例与需求之间的映射关系,尤其是用例是否真正能够覆盖对应的测试需求,也可能是存在疑问的。

测试的目的是发现软件缺陷。因此,衡量测试完整性的终极指标应该是面向测试结果的缺陷覆盖率,即测试所实际发现的缺陷数量与测试所应该发现的缺陷总量的比值

软件测试一般是分为多个测试阶段的。每个阶段有每个阶段的任务。对于当前测试阶段来说,在后续阶段发现的缺陷中,属于当前阶段(而不是更早阶段)遗漏出去的缺陷是我们需要重点关注的。

虽然讨论缺陷覆盖率有一种“事后诸葛亮”的感觉,但是它的意义是不容忽视的。一方面它提供了评价某一阶段测试工作成效的重要指标,另一方面它为我们改进测试工作提供了重要参考。例如,针对每一个遗漏缺陷深入挖掘,举一反三,可以避免同类问题在未来再次发生。

总结一下,这里介绍了三种常见的测试覆盖率的定义和度量方法,分别是代码覆盖率,需求覆盖率和缺陷覆盖率。它们适用于不同的场景,有各自的优势与不足。需要注意的是,它们不是互相排斥,而是相互补充的。

关于测试覆盖率,最重要的一点应该是迈出第一步,即有意识地去收集这种数据。没有覆盖率数据,测试工作会有点像在“黑灯瞎火”中走路。有了覆盖率数据,并持续监测,利用和改进这个数据,才是一条让测试工作越来越好的光明大道。

 1. 什么是内存对齐我们通过一段 C 语言程序,解释什么是内存对齐。理论上,下面的程序在 32 位系统下,int 占 4 byte,char 占一个 byte,那么将它们放到一个结构体中应该占 4+1=5 ...