Jacoco + 覆盖率平台在 Mercury 白盒测试中的实践

发表于:2017-9-07 15:16

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:施勤    来源:51Testing软件测试网采编

  白盒测试概述
  白盒测试是基于对系统内部一定了解之上的测试技术。测试人员需要拥有源代码相关权限与对系统架构的了解。测试人员需要进行源代码分析,在此基础上再根据源代码设计测试用例,并最终达到一定的代码覆盖率
  白盒测试关注点包括:安全漏洞、不可用或者不完整的路径、与相关说明文档的一致性、输出结果是否预期、所有的条件循环语句等。
  广义上来讲,白盒测试包括如下两种方法:
  ●静态白盒测试
  浏览代码,凭借经验,找出代码中的错误或者代码中不符合书写规范的地方,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: 创建与执行测试用例
  根据白盒测试技术(语句、条件、判断等)编写并执行测试用例。下面简单讲述一下白盒测试技术。
  白盒测试技术
  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 个测试用例来遍历所有独立路径。
  圈复杂度(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>

《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号