同学,听说你的测试覆盖率不准

现在很多项目都在做测试覆盖率统计,以此来提高项目信心;并且可以通过查看未被覆盖的代码找出潜在的遗漏测试用例,对于测试补充是一种很好的方法。

在文章JAVA代码覆盖率统计利器Jacoco中,较为详细的说明了怎么配置jacoco来获取java程序的覆盖率。但是在实际应用中,有很多同学反馈说感觉覆盖率不准,有些人可能只是觉着覆盖率不应该这么低,有些人可能看了覆盖率的具体情况,发现自己用例明显覆盖的行却显示没有被覆盖。

杭研的平台测试一组,大部分支持的项目也是java的,小组成员也经常反馈说覆盖率不准,最近通过分析和尝试,找到了覆盖率不准的原因和推荐解决的方法,如果大家也有java语言覆盖率不准的问题,可以作为参考;

在接触的大量的覆盖率不准的问题中,有90%都是由于同一个问题导致的:即class文件不一致。

该问题具体描述如下:由于jacoco在统计覆盖率时,是通过探针注入的方式来统计覆盖率的,所以如果生成的exec文件(即覆盖率文件)的class文件和用来解析exec文件的class文件不一样时,就会有“覆盖率统计不准”的问题;

现有持续集成过程

平台一组都是建议用pipeline的方式来进行每日的持续集成,该持续集成包含了单元测试和集成测试,测试覆盖率是统计两者的总和:

主要流程如下:

  1. 在ci节点机上进行单元测试,该次单元测试,会生成单元测试的unit.exec和class文件(该处文件我们叫class A);
  2. 通过部署脚本重新部署测试服务器(该服务器是在测试环境中);
  3. 通过执行接口测试和ant脚本,生成it.exec,需要注意的是,这个exec是依赖测试服务器上的class文件(该处文件我们叫class B)生成的;
  4. 最后,覆盖率统计的job,依赖class A对unit.exec和it.exec文件进行解析,生成覆盖率问题;

从上面的流程可以看出:接口测试覆盖率不准的原因,是由于解析exec文件的class文件不正确;

那如何解决这个问题呢,可能有人说,那就用class B来解析it.exec文件就好,但是这样的就导致单元测试覆盖率不准,对总体的覆盖率统计也同样有影响;所以,终极解决方法就是保证生产两个exec文件的class文件是相同的

最好的解决方法就是在构建测试服务器上的class文件时,同时将单元测试执行完,这样就可以完美解决该问题;但是现在在基本都是用ndp来构建和部署测试服务器,所以我们的目标就是在ndp构建时,将单元测试执行完,并且将结果拷贝到节点机上解析;这样不仅解决了覆盖率的问题,单元测试的结果等,也都可以正常的查看;

具体解决思路如下:

项目只有集成测试,没有单元测试

对于这种情况,其实很好处理,就是在解析覆盖率时,使用测试服务器上的class文件来解析覆盖率,具体方式有如下几种,可以任选一种解决:

  1. 直接在测试服务器上,利用class文件进行解析;
  2. 将测试服务器上的class文件cp到客户端机器,再进行解析;
  3. 如果是使用ndp等自动发布工具发布的代码,可以通过其接口来下载构建的class文件;

此次需要注意的是,下载或者scp来的class文件,需要放到对应的目录,如果放置不正确,会报找不到class文件的错误;

当然,也可以指定class文件的目录(此处以sonar解析为例)

-Dsonar.java.binaries=${path}/classes

项目有单元测试和集成测试代码,需要两者都统计

如果你的项目和平台一组一样,单元测试和集成测试的覆盖率都要统计,那么也是有解决方法:

  1. 在构建部署到测试服务器上的class文件时,将单元测试执行;
  2. 使用测试服务器上的class文件来解析单元测试和集成测试结果;

同样需要注意cp来的class文件放置的目录需要正确

最佳实践

下面以测试平台一组的job为例,说明如何修改jekins的job来解决覆盖率不准的问题。其他项目也可以根据情况,选择最适合自己的修改方式。修改的原则就是:解析exec文件的class文件,需要和产生exec的class文件一致

需要说明的是,这边构建和部署发布都是通过ndp进行,所以示例也以此来说明,主要说明修改思路。

最佳实践中使用的脚本等,是封装ndp接口,相对来说较为简单,所以不详细描述如何实现。

1、修改ndp构建的build.xml文件,在构建时执行单元测试;

在ndp页面上修改build.xml文件,需要修改两处:

1.1 在打包时,修改为执行unit test的命令,注意需要增加-Dmaven.test.failure.ignore=true参数,防止单元测试失败以后构建失败

2.1 是在最后cp的步骤中,需要将整个构建的结果都cp过来,这边主要是因为一般构建文件中,是只cp了class文件;此处修改是为了ndp在将构建的结果上传nos的时候,将单元测试的结果也上传上去

2、修改coverage job,进行静态代码检查等工作

调整后的coverage的job主要完成以下事情:

a、拉取开发代码;

b、下载单元测试结果;

c、统计代码覆盖情况;

d、通过sonar进行静态代码检查和覆盖率统计

2.1 不再使用自定义的工作空间

之前的coverage job,一直都是用unittest job的工作空间的,现在由于unittest job已经不在使用,所以此处需要将【使用自定义的工作空间】选项去除:

2.2 增加源码管理,拉取开发代码

这个和之前的unittest中的配置一致,详细配置就不再叙述:

2.3 修改shell命令,下载class文件和单元测试覆盖率文件

这边通过ndp接口下载最新的构建包,并进行相关处理,需要注意class文件需要cp到target目录下:

脚本说明:

python $build_path/ndp_deploy.py -d 下载文件名称 -c 集群id -u 发布人邮箱前缀

ndp_deploy.py脚本在指定-d参数时,会下载-c参数指定集群的最新的构建包

效果

从作者负责的项目改造的情况来看,同样的39个用例,接口测试覆盖率从14%提升到了30%,还是有很大提升的。最重要的时,如果class文件和exec文件不匹配,那么查看具体文件覆盖率时,显示的是不正确的,对测试补充等都会有误导。所以还是强烈建议大家解决该问题。

改造前:

改造后:


小提醒

需要注意的是,在普通统计和分析覆盖率文件时,有3个地方用到jacoco,需要保证这3个版本的统一:

  1. 单元测试中使用的jacoco版本;
  2. 测试服务器上的jacoco版本;
  3. 生成it.exec的jacoco版本;

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者徐晗授权发布。