0


如何理解软件的测试覆盖率?

测试覆盖率通常用来衡量测试的充分性和完整性。

从广义来讲,大致分为业务层面的需求覆盖率和技术层面的代码覆盖率

一、需求覆盖率

通常通过需求管理工具,来建立需求和测试用例的对应关系,并以此来计算测试覆盖率。

需求覆盖率的统计方法属于比较重量级的方法体系,属于传统瀑布模式下的软件工程实践,很难适应当今互联网时代下的敏捷开发实践。

所以,互联网测试项目中很少基于需求来衡量测试覆盖率,而是将软件需求转换成测试需求,因此互联网企业中涉及的需求覆盖率通常默认指代码覆盖率

只有少数沿用瀑布开发模型的传统型软件企业还在继续使用需求覆盖率来衡量测试的完备性。

二、代码覆盖率

简单来说,代码覆盖率是指,至少执行了一次的条目数占整个条目数的百分比。

如果"条目数"是语句,对应的就是行覆盖率

如果"条目数"是函数,对应的就是函数覆盖率

如果"条目数"是路径,对应的就是路径覆盖率

简单介绍一下最常用的几种代码覆盖率指标:

  • 行覆盖率又称语句覆盖率,指已执行的语句占全部可执行语句(不包含类似C++的头文件声明,代码注释,空行等)的百分比。这是最常用也是要求最低的覆盖率指标。实际项目中通常会结合判定覆盖率和条件覆盖率一起使用。
  • 函数覆盖率又称方法覆盖率,指的是执行到的函数占总函数数量的百分比。
  • 判定覆盖率又称分支覆盖率,用于度量程序中的每一个判断的分支是否都被测试到了,即代码中的每个判断的取真分支和取假分支是否均覆盖到了最少一次,比如对于if(a>0&&b>0)就要求覆盖true和false各一次。
  • 条件覆盖率是指每个条件的取值至少满足一次,用于度量判断的每个条件的结果(true和false)是否都测试到了,比如对于if(a>0&&b>0),要求a>0取到true和false各一次,b>0也要取到true和false各一次。
  • 条件/判断覆盖率指需要同时满足判断覆盖率和条件覆盖率。
  • 修改条件/判断覆盖率是一个要求最高的覆盖率指标。在一些与安全息息相关的应用中,一般会需要满足修改条件判断覆盖的准则。此准则是条件/判断覆盖的延伸,而且每个条件都要影响判断结果成立还是不成立。例如以下语句:
if (a or b) and c then

以下测试可以满足条件/判断覆盖率

a=true,b=true,c=true

a=false,b=false,c=false

不过若要求第一项测试中的b的值改为false,不影响判断结果,第二项测试中的c的值改为true,不影响判断结果,就需要用以下的测试来满足修改条件/判断覆盖率

a=false,b=false,c=true

a=true,b=false,c=true

a=false,b=true,c=true

a=true,b=true,c=false

其中粗体的条件表示影响判断结果的条件。在影响判断结果的条件中,每个变量都出现至少两次,其中至少一次其值为true,至少一次其值为false。

代码覆盖率的价值

现在很多项目都在单元测试以及集成测试阶段统计代码覆盖率,但是统计代码覆盖率仅仅是手段,我们必须透过现象看事务的本质,才能从根本上来保证软件整体的质量。

统计代码覆盖率的根本目的是找出潜在的遗漏测试用例,并针对性的进行补充,同时还可以识别出代码中那些由于需求变更等原因造成的不可用的废弃代码

通常我们希望代码覆盖率越高越好。代码覆盖率越高,越能说明测试用例设计是充分且完备的。但是你也发现测试成本随着代码覆盖率的提高以接近指数级的方式迅速增加。如果想达到70%的代码覆盖率,可能需要30min的时间,但是如果想达到90%,就会花费远远不止30min的时间。而想要达到100%的代码覆盖率,花费的时间成本就会更高。

为什么代码覆盖率的提高,需要付出越来越大的代价呢?

因为在后期需要大量的桩代码,Mock代码,和全局变量的配合来控制执行路径。

所以软件企业中只有单元测试阶段对代码覆盖率有较高的要求。因为从技术实现上讲,单元测试可以最大化地利用打桩技术来提高覆盖率。而在集成测试或GUI测试阶段将代码覆盖率提到一定百分比,所要付出的代价将是巨大的,而且在很多情况下根本就实现不了。

代码覆盖率的局限性

如果你通过努力,已经把某个函数的MC/DC(修改条件/判断覆盖率,代码覆盖率的最高标准,一般项目除了直接关系人生命安全,很少会严格要求MC/DC)提高到了100%,软件质量就真的高枕无忧,万无一失了吗?

即使你所设计的用例已经达到100%的代码覆盖率,软件产品的质量也做不到万无一失,其根本原因在于代码覆盖率的计算是基于现有代码的,并不能发现那些"未考虑某些输入"以及"未处理某些情况"形成的缺陷。

例如,如果一个被测函数里面只有一行代码,只要这个函数被调用了,那么衡量这一行代码质量的所有覆盖率指标都会是100%,但是这个函数是否真正实现了应该需要实现的功能呢?显然,代码覆盖率反映的仅仅是已有代码的哪些逻辑执行过了,哪些逻辑还没有执行过,以此为依据,可以补充测试用例,可以以测试那些还没有覆盖到的执行路径,但仅此而已。对于那些完全还没有代码实现的部分就无能为力了。

总体来讲,高的代码覆盖率不一定能保证软件的质量,但是低的代码覆盖率一定不能保证软件质量。

关于代码覆盖率的报告

以统计Java语言代码覆盖率的工具JaCoCo为例,介绍一下代码覆盖率工具生成的统计报告。

JaCoCo可以很方便的嵌入到Ant,Maven中,并且和很多主流的持续集成工具以及代码静态检查工具(如Jenkins和Sonar等)有很好的集成。

代码覆盖率统计报告,包括了每个Java代码文件的行覆盖率,分支覆盖率的统计,并给出了每个Java代码文件的行数,方法数和类数等具体信息。

文件内部详细的代码覆盖率如下图所示:

其中绿色的行表示已覆盖,黄色的行表示部分覆盖,红色的行表示未覆盖。

左侧绿色菱形块表示该分支已经完全覆盖,黄色菱形块表示分支仅部分覆盖,红色菱形块表示分支未覆盖。

显然,通过这个详尽的报告,就可以知道代码的真实执行情况,哪些代码未被覆盖。以此为基础再去设计测试用例就会更有针对性了。

代码覆盖率工具的实现技术

实现代码覆盖率的统计,最基本的方法就是注入。简单地说,注入就是在被测代码中自动插入用于覆盖率统计的探针(Probe),并保证插入的探针代码不会对原代码带来任何影响。

对于Java代码来讲,根据注入目标不同,可以分为源代码注入和字节码注入两大类。

基于JVM本身特性和执行效率的原因,目前主流工具基本都是使用字节码注入,注入的具体实现采用ASM技术,ASM是一个Java字节码操纵框架,能用来动态生成类或者增强既有类的功能,可以直接产生class文件,可以在类加载到JVM中之前动态改变类行为。

根据注入发生的时间点,字节码注入又可以分两大模式:即时注入(On-The-Fly)模式和离线注入(Offline)模式。

1.即时注入模式

即时注入模式的特点在于无须修改源代码,也无需提前进行字节码插桩,适用于支持Java代理的运行环境。这样做的优点是可以在系统不停机的情况下,实时收集代码覆盖率的信息。缺点是运行环境必须使用Java代理。

实现即时注入模式主要有两种技术方案。

(1)开发自定义类加载器,实现类装载策略,每次加载类前,需要在Class文件中插入探针,早期Emma就是使用这种方式实现探针插入。

(2)借助Java代理,利用在main()方法之前执行的拦截器方法premain()来插入探针,实际使用过程中需要在JVM的启动参数中添加“-javaagent”,并指定用于实现字节码注入的代理程序,这样代理程序在装载每个Class文件前,先判断是否已经插入了探针,目前主流的JaCoCo就使用这种方式。

2.离线注入模式

离线注入模式也无须修改源代码,但是需要在测试开始之前先对文件进行插桩,并提前生成插过桩的Class文件。它适用于不支持Java代理的运行环境,以及无法使用自定义类加载器的场景。这样做的优点是,JVM启动时不再需要额外开启代理,缺点是无法实时获取代码覆盖率信息,只能在系统停机时获取。

离线模式根据生成新的Class文件还是直接修改原Class文件,又可分为替换和插入两种模式。和即时注入不同,替换和插入,在测试运行前就已经通过ASM将探针插入了Class文件,而在测试的运行过程中不需要任何额外的处理。Cobertura就是使用离线模式的典型代表。

标签: 单元测试

本文转载自: https://blog.csdn.net/hetao940527/article/details/124082074
版权归原作者 核桃zet 所有, 如有侵权,请联系我们删除。

“如何理解软件的测试覆盖率?”的评论:

还没有评论