美文网首页
Jacoco + 覆盖率在白盒测试中的实践(转)

Jacoco + 覆盖率在白盒测试中的实践(转)

作者: 烨枫_邱 | 来源:发表于2019-03-20 18:05 被阅读0次

白盒测试概述

白盒测试是基于对系统内部一定了解之上的测试技术。测试人员需要拥有源代码相关权限与对系统架构的了解。测试人员需要进行源代码分析,在此基础上再根据源代码设计测试用例,并最终达到一定的代码覆盖率

白盒测试关注点包括:安全漏洞、不可用或者不完整的路径、与相关说明文档的一致性、输出结果是否预期、所有的条件循环语句等。

  广义上来讲,白盒测试包括如下两种方法:

静态白盒测试

  浏览代码,凭借经验,找出代码中的错误或者代码中不符合书写规范的地方,CodeReview 是一种不错的方式。

比如下面的代码,表示 Mercury 每30秒进行 metrix 落盘记录

  this.registry = MetricsRegistryBuilder.create().setPollingInterval(30000L).setLoggerReporterName(loggerName).build();

  但相关处理的代码如下:

  ...

  result.setStartTime(ts);

  result.setEndTime(ts + 1000);

  ...

  这里的startTime与endTime的差值一直为1000ms,所以导致无论setPollingInterval传递参数是多少,落盘数据的时间始终为1000ms。

动态白盒测试

  通过执行/调试过程,遍历代码各分支来进行测试;

  白盒测试一般包括如下两个步骤:

Step 1:理解源代码

  熟悉系统所用的编程语言,有时候同样需要有系统安全性的相关知识。

Step 2: 创建与执行测试用例

  根据白盒测试技术(语句、条件、判断等)编写并执行测试用例。下面简单讲述一下白盒测试技术。

白盒测试常用工具

代码分析检查工具:FindBugs、PMD

覆盖率工具:jacoco、EMMA

白盒测试工具:junit、testng

质量管理平台:SonarQube

编译工具:Ant、maven

Mock工具:Jmockit

数据库:mysql

持续集成工具:Jenkins

代码管理工具:SVN、git

白盒测试技术

1. 语句覆盖(Statement Coverage)

  被测代码中每个可执行语句是否被执行到。下图是 Mercury 白盒测试中的覆盖率截图。其中绿/黄色行表示被执行到的语句,红色行表示未被执行到(黄色行的含义见后面的 Jacoco 中的分支和条件覆盖率)。

2. 分支覆盖(Branch Coverage)

  代码中每个分支是否都被覆盖。对于 if 语句,true 和 false 分支都走到了,才能说全部分支都覆盖了;对于 switch-case 需要每个 case 和 default 都需要走到。

3. 条件覆盖(Condition Coverage)

  每个判断中每个条件的可能取值至少满足一次

  比如,对于如下条件语句

  if ( a > 5 && b < 3 ) {

      ... 

  }

  有以下4种场景用例才能覆盖全:

  a > 5 && b >= 3 【真&&假,结果:false】

  a <= 5 && b < 3 【假&&真,结果:false】

  a <= 5 && b >= 3 【假&&假,结果:false】

  a > 5 && b < 3 【真&&真,结果:true】

4. 路径测试

  路径测试可以保证一个模块中的所有独立路径至少被使用一次。具体操作上,可以先画出流程图、计算圈复杂度、再根据独立路径来设计测试用例。

  以下为 Mercury 中一段校验 HTTP 参数的代码:

  if (null == component.getCode() || component.getCode().trim().isEmpty()) {

      response.setCode(BaseResponse.PARAM_ERROR);

      response.setMsg("参数(code)缺失");} else if (component.getCode().length() > 64) {

      response.setCode(BaseResponse.PARAM_ERROR);

      response.setMsg("Code长度不得超过64个字符");} else if (component.getName() != null && component.getName().length() > 64) {

      response.setCode(BaseResponse.PARAM_ERROR);

      response.setMsg("Name长度不得超过64个字符");} else if (component.getDescription() != null && component.getDescription().length() > 255) {

      response.setCode(BaseResponse.PARAM_ERROR);

      response.setMsg("描述长度不得超过255个字符");} else {

      MetricComponentType exist = metricConfigService.getComponent(component.getCode());

      if (exist != null) {

          response.setCode(BaseResponse.PARAM_ERROR);

          response.setMsg("相同配置(" + component.getCode() + ")已存在");

      } else {

          response.setData(metricConfigService.createComponent(component));

          response.setCode(BaseResponse.SUCCESS);

          response.setMsg("ok");

      }

  }

  代码中有 5 个判断,因而 判定节点个数为5,由于 圈复杂度(独立路径个数)= 判定节点 + 1,因而以上代码圈复杂度为:5 + 1 = 6,我们至少需要 6 个测试用例来遍历所有独立路径。

5. 圈复杂度(cyclomatic complexity)

  用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护。根据经验,程序的可能错误和高的圈复杂度有着很大关系。代码复杂度增加会导致几乎不可能画出流程图并且计算出圈复杂度。

Jacoco 中的分支和条件覆盖率

  很多统计覆盖率的工具中并没有区分条件覆盖和分支覆盖。举个例子,下面的 Example类中的 if 语句,看上去测试类 BranchCoverageTest 应该覆盖了两个分支。但 Jacoco 的报告中却显示 2 of 6 branches missed.。

  package com.test;

  public class Example {

      public boolean branchFunc(int x, int y, int z) {

          if (x > 0 || y > 0 || z > 0) {

              return true;

          } else {

              return false;

          }

      }

  }

  import com.test.Example;

  import org.junit.Test;

  public class BranchCoverageTest {

      @Test

      public void testBranchCoverage(){

          Example bct = new Example();

          bct.branchFunc(0, 0, 0);

          bct.branchFunc(1, 0, 0);

      }

  }

   其实在 Jacoco 里,每个 Yes、No 都是 1 个 Branch。如下图所示,B1、B2、B3、B4、B5、B6 都是一个 branch。上述用例其实只覆盖了 B1、B4、B5、B6 而 B2、B3 并没有覆盖,所以最终的 branch coverage 为 66%。

  在 test case 里增加 (0, 1, 0), (0, 0, 1) 最终的测试结果中 branch coverage 为 100%。

常用的覆盖率指标

行覆盖率

  度量被测程序的每行代码是否被执行,判断标准行中是否至少有一个指令被执行。

类覆盖率

  度量计算class类文件是否被执行。

分支覆盖率

  度量if和switch语句的分支覆盖情况,计算一个方法里面的总分支数,确定执行和不执行的 分支数量。

方法覆盖率

  度量被测程序的方法执行情况,是否执行取决于方法中是否有至少一个指令被执行。

指令覆盖

计数单元是单个java二进制代码指令,指令覆盖率提供了代码是否被执行的信息,度量完全 独立源码格式。

圈复杂度

  在(线性)组合中,计算在一个方法里面所有可能路径的最小数目,缺失的复杂度同样表示测 试案例没有完全覆盖到这个模块。

Example: 利用覆盖率平台提升 Mercury 覆盖率

  覆盖率平台是 VIP 自主研发的内部用于查看用例覆盖程度的工具。怎么使用测试覆盖率这里不进行说明,感兴趣的读者可查看历史消息 浅谈唯品会测试覆盖率平台。

工作中,我们可以使用测试覆盖率平台分析代码来提高测试覆盖率,通过下面浅显易懂的例子来说明。

  在一次 Mercury 的功能变动后,在覆盖率平台上看到一块处理代码的覆盖率急剧下降(如下图所示),只剩下 else 语句被覆盖:

  可以看到独立路径只覆盖了1个,分支覆盖率也为50%:

 跟之前说明过的一样,红色为未覆盖的路径,分析得到我们需要提高独立路径覆盖率,这里需要额外7个测试用例:

  ●使用非管理员账号发送请求

  ●请求参数中不填写开始或者结束时间

  ●请求参数中开始时间大于结束时间

  ●请求参数中不包含name

  ●请求参数中name长度256

  ●请求参数中不含有item

  ●请求参数中item长度256

  ●另外要实现分支覆盖,针对

  else if (model.getStart() == null || model.getEnd() == null)

  我们使用这8个测试用例:

  使用非管理员账号发送请求

  请求参数中不填写开始时间,有结束时间

  请求参数中有开始时间,没有结束时间

  请求参数中开始时间大于结束时间

  请求参数中不包含name

  请求参数中name长度256

  请求参数中不含有item

  请求参数中item长度256

  最后覆盖率结果,独立路径覆盖率与分支覆盖率都达到了100%:

  Note: 该例中因 else 和最后的 “}“ 使得行覆盖率只有 90%,小于分支覆盖率。 由于分支覆盖中包含各种条件,比如之前说的 if (x >0 || y > 0 || z >0 ) 例子,两个TC就能使得行覆盖率达到 100%, 但要让分支覆盖达到 100% 却需要 4个 TC,因而 分支覆盖率相对行覆盖率来说更严格。

白盒测试的利弊

  每种测试方法都有其利弊,白盒也不例外。这里列举一些白盒测试的利弊,可以在权衡之后选择是否进行白盒测试:

Pros:

  不依赖于GUI就可以进行白盒测试

  可以帮助覆盖全路径

  测试人员能够对代码提出改进建议

  因为测试人员了解代码内部结构,可以使用更高效的测试数据

  白盒测试能够更好地促进优化代码

Cons:

  需要对代码内部结构有深入了解的高技术人员来进行测试,提高了成本

  如果代码频繁变动就需要更新测试脚本

  如果应用测试量很庞大,完全测试是不可能的

  不可能测试系统的每个路径或者条件(而路径/条件可能有缺陷)

  白盒测试相对代价大

  分析每行每条路径几乎是不可能的

  需要使用不同的输入条件来测试每条路径或者条件,所以测试人员需要准备大量测试数据,而这个过程可能很耗时。

  Appendix : Jacoco Maven plugin 的配置

  在 pom.xml 中按下面例子配置,加入对 Jacoco Maven plugin 的依赖,即可在执行 mvn test 命令后生成覆盖率报告(位于 target/site/jacoco 目录下)。

Note:

  如果将 <phase>test</phase> 改成 <phase>prepare-package</phase> 在运行 mvn test 是不会出报告的,需要运行 mvn package 才能看得到。具体原因,大家可了解下 maven project 的生命周期。

  有关 Jacoco Maven plugin 定义的 goal 及相关参数配置,可去 http://www.eclemma.org/jacoco/trunk/doc/maven.html 查看。

  <plugin>    

     <groupId>org.jacoco</groupId>

     <artifactId>jacoco-maven-plugin</artifactId>

     <version>0.7.9</version>

     <configuration>

         <!-- 指定需要统计覆盖率的类,jacoco 有坑见这里在尾部加个 * 作为 workaround

           可见 https://github.com/jacoco/jacoco/issues/34 -->

        <includes>

          <include>com/test/Example*</include>

        </includes>

     </configuration>

     <executions>

       <execution>

          <!-- 在maven的initialize阶段,将Jacoco的runtime agent作为VM的一个参数

            传给被测程序,用于监控JVM中的调用。-->

         <id>default-prepare-agent</id>

         <goals>

            <goal>prepare-agent</goal>

         </goals>

         <configuration>

            <destFile>${project.build.directory}/coverage-reports/jacoco.exec</destFile>

          </configuration>

       </execution>

       <execution>

           <id>default-report</id>

          <phase>test</phase>

          <goals>

            <goal>report</goal>

          </goals>

          <configuration>

             <dataFile>${project.build.directory}/coverage-reports/jacoco.exec</dataFile>

             <!-- 过滤 report 中需要展示/不展示的类 -->

             <!--<includes>com/test/*</includes>-->

             <!--<excludes>annot/*</excludes>-->

             <outputDirectory>${project.reporting.outputDirectory}/jacoco</outputDirectory>

          </configuration>

       </execution>

       <execution>

          <id>default-check</id>

          <goals>

            <goal>check</goal>

          </goals>

       </execution>

      </executions>

  </plugin>

Jacoco统计白盒测试覆盖率

原理: 自动插桩

JVM中通过-javaagent参数指定特定的jar文件启动代理程序,代理程序在通过Class Loader装载一个class前判断是否转换修改class文件,将统计代码插入class,测试覆盖率分析可以在JVM执行测试代码的过程中完成。

效果:

基于maven,在server test里面编写白盒测试代码,在pom文件加入下面的build信息,run as install

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-surefire-plugin</artifactId><configuration><includes><include>**/*BaseDaoTest.java</include></includes><excludes><exclude>**/Abstract*.java</exclude></excludes></configuration></plugin><plugin><groupId>org.jacoco</groupId><artifactId>jacoco-maven-plugin</artifactId><version>0.5.3.201107060350</version><executions><execution><goals><goal>        prepare-agent

      </goal></goals></execution><execution><id>JaCoCo Report</id><phase>prepare-package</phase><goals><goal>        report

      </goal></goals></execution></executions></plugin></plugins></build>

 操作:

1.写好单元测试代码

2.上面的maven build,获取覆盖率报告

具体报告,可以帮助我们分析哪里覆盖率做的不好,去优化我们的白盒测试代码,覆盖:

target/site/jacoco/index.html

行覆盖率:度量被测程序的每行代码是否被执行,判断标准行中是否至少有一个指令被执行。

类覆盖率:度量计算class类文件是否被执行。

分支覆盖率:度量if和switch语句的分支覆盖情况,计算一个方法里面的总分支数,确定执行和不执行的 分支数量。

Jacoco注入方式介绍

  jacoco注入方式图

  说明:

  1、 JaCoCo在Byte Code时使用的ASM技术修改字节码方法,可以修改Jar文件、class字节码文件。

  2、JaCoCo同时支持on-the-fly和offline的两种插桩模式。

  3、 On-the-fly模式中, JVM中通过-javaagent参数指定特定的jar文件启动Instrumentation的代理程序,代理程序在通过Class Loader装载一个class前判断是否转换修改class文件,将统计代码插入class,测试覆盖率分析可以在JVM执行测试代码的过程中完成。

  4、offline模式中,在测试前先对文件进行插桩,然后生成插过桩的class或jar包,测试插过桩 的class和jar包后,会生成动态覆盖信息到文件,最后统一对覆盖信息进行处理,并生成报告。

 On-the-fly和offline比较:

  On-the-fly模式更方便简单进行代码覆盖分析,无需提前进行字节码插桩,无需考虑classpath 的设置。

  存在如下情况不适合on-the-fly,需要采用offline提前对字节码插桩:

  (1) 运行环境不支持java agent。

  (2) 部署环境不允许设置JVM参数。

(3) 字节码需要被转换成其他的虚拟机如Android Dalvik VM。

  (4) 动态修改字节码过程中和其他agent冲突。

  (5) 无法自定义用户加载类。

Jacoco最小支持java5

jacoco注入示例

jacoco注入示例图

mock工具:jmockit简介

        JMockit是google code上面的一个java单元测试mock项目,是基于Java中的java.lang.instrument包开发,内部使用ASM库来动态修改java的字节码,可以Mock公共方法、私有方法、接口等。

Jmockit常用注解

  @Mocked:被修饰的对象将会被Mock,对应的类和实例都会受影响(同一个测试用例中)

  @Injectable:仅Mock被修饰的对象

  @Capturing:可以mock接口以及其所有的实现类

  @Mock:MockUp模式中,指定被Fake的方法

Jmockit常用类

  Expectations:期望,指定的方法必须被调用

  StrictExpectations:严格的期望,指定方法必须按照顺序调用

  NonStrictExpectations:非严格的期望,是否调用和顺序不作要求

  Verifications:验证,一般配合NonStrictExpectations来使用

  Invocation:工具类,可以获取调用信息

  Delegate:自己指定返回值,适合那种需要参数决定返回值的场景,只需指定匿名子类就可以。

  MockUp:模拟函数实现

  Deencapsulation:反射工具类

Jmockit使用示例

jmockit使用示例图

代码质量管理平台:sonar简介

  Sonar是一个代码质量管理平台,通过插件机制可以支持20多种开发语言代码质量管理与检测。通过不同的插件对搜集的结果进行再加工处理,通过量化的方式度量代码质量的变化,从而可以方便地对不同规模和种类的工程进行代码质量管理。同时 Sonar 还对大量的持续集成工具提供了接口支持,可以很方便地在持续集成中使用 Sonar。

  Sonar可以从以下七个维度检测代码质量:

  不遵循代码标准

  潜在的缺陷

  复杂度分析

  重复率分析

  注释率分析

       单元测试覆盖率分析

  耦合度分析

我们先看看sonar分析展示图:

sonar分析展示图

  sonar分析展示图

Sonar与maven集成配置

  sonar与maven集成,只需修改maven的settings.xml文件,加入以下内容即可:

soanr与maven集成图

  soanr与maven集成图

Sonar与Ant集成配置

  soanr与Ant集成比较麻烦,需要在build.xml文件中引用相应的包,如:jacocoant.jar、sonar-ant-task-2.2.jar等。build.xml部分配置如下:

soanr与Ant集成图(一) soanr与Ant集成图(二)

白盒测试执行

  白盒测试执行比较简单,可与jenkins集成,在持续集成过程中执行,也可在本地执行。

  执行方式一:Jenkins构建过程中自动执行(jenkins+sonar+Ant/maven+jacoco+Findbugs集成)

  执行方式二:本地Eclipse编译过程中自动执行(jenkins+sonar+ant/maven+jacoco+Findbugs集成)

白盒测试报告

  白盒测试报告可在sonar平台查看,比较方便,具体不在累述。

相关文章

网友评论

      本文标题:Jacoco + 覆盖率在白盒测试中的实践(转)

      本文链接:https://www.haomeiwen.com/subject/ificvqtx.html