测试基础 jacoco 覆盖率与 git diff 结合的应用实践

syl7752 · 2016年10月24日 · 最后由 第一号伤心人 回复于 2019年06月18日 · 4480 次阅读
本帖已被设为精华帖!

前言

覆盖率是衡量测试质量的一个指标,但在版本高速迭代的互联网应用中,单纯通过代码覆盖率的数值是无法体现测试实际覆盖的问题,尤其是版本之间的 diff,而且研发也并不会仔细的去看每行代码的覆盖.所以有了覆盖率与 git diff 结合的想法,通过查看功能测试中版本代码变更的部分是否覆盖,更高效的利用覆盖率,保证变更功能被测试覆盖

覆盖率的收集

覆盖率使用的 jacoco 关于 jacoco 手工测试生成:

根据 q 博的方式略做了修改,在应用中加了隐藏开关,并在退出应用时统计覆盖率并上传到服务器.这里用的是 ftp,虽然有点过时,但还算方便~
代码很简单,就不贴了.

class 文件的收集

jacoco 的生成需要对应的 class 文件,所以需要在编译打包时将 class 文件上传

dependencies {
    ftpAntTask("org.apache.ant:ant-commons-net:1.8.4") {
        module("commons-net:commons-net:1.4.1") {
            dependencies "oro:oro:2.0.8:jar"
        }
    }
}
def remotedir = "Class/"  //ftp目录
//def hostname = "10.0.100.210"
def hostname = "10.0.0.243"
def username = "uftp"
def password = "111111"

task uploadFTP << {
    //上传至FTP
    remotedir += innerVersion
    ant {
        taskdef(name: 'ftp',
                classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
                classpath: configurations.ftpAntTask.asPath)
        ftp(server: hostname,
                userid: username,
                password: password,
                remotedir: remotedir,
                action: 'mkdir')
        ftp(server: hostname,
                userid: username,
                password: password,
                remotedir: remotedir) {
            fileset(dir: "./build/intermediates/classes") {
            }
        }
    }
}

在 jenkins 上覆盖率报告生成

首先在 job 里添加了三个可选参数,用于选择新旧版本以及分支

构建时进行 jacoco 文件的合并和报告的生成,并调用我写的 java 程序改造报告.

# generate jacoco report
gradle jacocoMerge --stacktrace
gradle jacocoTestReport

# generate final report
java -jar ~/work/gitdiff.jar -r $OldVersion $NewVersion -j build/reports/jacoco/jacocoTestReport/html -p src/main/java
cp -r ~/work/.resources build/reports/jacoco/jacocoTestReport/

在程序中调用 git diff, 并对结果进行解析

public HashMap<String,FileDiff> parseGitDiff()
     {
         HashMap<String,FileDiff> fileMap=new HashMap<String,FileDiff>();
         ArrayList<String> curFile=new ArrayList<String>();
         for (int i = 0; i < diffList.size(); i++) {
             curFile.add(diffList.get(i));
            if((i+1)==diffList.size()||diffList.get(i+1).startsWith(GIT_FLAG))
            {
                FileDiff diff=new FileDiff(curFile);
                fileMap.put(diff.getId(), diff);
                curFile=new ArrayList<String>();
            }
        }
         return fileMap;
     }

public ArrayList<LineDiff> parseLineDiff(ArrayList<String> files)
    {
        ArrayList<LineDiff> diff=new ArrayList<LineDiff>();
        boolean isStart=false;
        String frontLine="";
        for (int i = 0; i < block.size(); i++) {
            String line=block.get(i);


            if((i+1)<block.size()&&block.get(i+1).startsWith(MINUS_FLAG)&&!isStart)
            {
                isStart=true;
                frontLine=block.get(i);
            }
            if(line.startsWith(MINUS_FLAG)&&isStart)
            {
                LineDiff lineDiff=new LineDiff(frontLine, line,LineDiff.Type.Minus);
                diff.add(lineDiff);
            }
            if(i>1&&!block.get(i-1).startsWith(MINUS_FLAG)&&!line.startsWith(MINUS_FLAG))
            {
                frontLine=block.get(i-1);
                isStart=false;
            }
            if(line.startsWith(PLUS_FLAG))
            {
                LineDiff lineDiff=new LineDiff(frontLine,line,LineDiff.Type.Plus);
                diff.add(lineDiff);
            }

        }
        return diff;
    }

代码写的不太好,能力强的同学可以忽略~
将 git diff 提取出来后找到对应覆盖率 html 并写入.从而产生了一个只有代码变更文件的 list
效果:

然后更改 jacoco 原来的 prettify.js 文件,对变更代码进行标记

function showDef (defLine) {
    var beginLine = defLine[0].substring(1);
    var between = defLine[1];

    var target = document.getElementsByClassName('linenums')[3];
    var defDom = document.createElement('div');

    var beginDom = target.getElementsByTagName('li')[beginLine - 1];
    with(defDom.style){
      position = 'absolute';
      left = '9px';
      top = beginDom.offsetTop + 16 + 'px';
      backgroundColor = '#0f0';
      opacity = '.3';
      width = '40px';
      height = 15 * between + 'px';
    };
    defDom.id = 'def' + beginLine;
    document.documentElement.appendChild(defDom);
  }

对变更代码进行跳转

function showDefLine () {
    var content = window['CONTENT'];
    var def = content.match(/@@(.*[^@@])/g);
    for (var i = def.length - 1; i >= 0; i--) {
      var _def = def[i].replace(/@@/g, '').split(' ');
      showDef(_def[2].split(','));
    };

    var sourceDom = document.getElementsByClassName('linenums')[1];
    sourceDom = sourceDom.getElementsByTagName('span');
    for (var i = sourceDom.length - 1; i >= 0; i--) {
      if(sourceDom[i].innerHTML === '@@'){
        sourceDom[i].parentNode.onclick = function () {
          document.body.scrollTop = document.getElementById('def' + this.getElementsByTagName('span')[9].innerHTML).offsetTop - 100;
        }
      }
    };
  }

修改后的效果如下,页面 top 显示 diff 代码

左侧绿色标记 diff 代码

最后,写的很匆忙,有点乱,勿怪~

共收到 23 条回复 时间 点赞
思寒_seveniruby 将本帖设为了精华贴 10月24日 19:10

加精理由: 体现了社区的技术传承与创新. 弥补了社区缺乏的白盒分析技术点.

我记得有赞之前做过一个自动把覆盖率上传到远程服务器, 实时生成报告的技术 . http://tech.youzan.com/code-coverage/
非常不错, 值得参考

#3 楼 @seveniruby 感谢思寒分享,回头参考改进下~

.从而产生了一个只有代码变更文件的 list

楼主 怎么样从全量的 html 里过滤出变更的 list 的,这个伪代码能不能告诉下?

#5 楼 @wangyijie7 在解析 git diff 的后就已经可以获得一个变更的 list 了 在过滤全量的 html 文件时,根据包名及类名获得对应的 java.html 然后提取出来

楼主你好,请问 jacoco 覆盖率是否是建立在有单元测试代码的项目里面的? @syl7752

#7 楼 @simple 这里统计的是手工测试的覆盖率 没有单元测试的

@syl7752 就是过滤 html 文件是吧 明白了,谢谢了,多交流.

#8 楼 @syl7752 可以覆盖到 jar 包里面的代码吗?

#10 楼 @simple 应该是不能

#10 楼 @simple 我的意思是单纯使用是不能的 不知道有没有办法实现

lz,这个可以计算出覆盖率吗

#14 楼 @gugutian 是可以的 只不过比较麻烦 需要统计覆盖的行数与变更的行数

#14 楼 @gugutian 是可以的 只不过比较麻烦 需要统计覆盖的行数与变更的行数

有木有 demo 可以参考下呀,感觉 diff 代码标记部分有点复杂😄

“然后更改 jacoco 原来的 prettify.js 文件,对变更代码进行标记”
prettify.js 文件 哪里调用 showDef() 和 showDefLine() 这两个 function 呢?

#18 楼 @cryingdream94 showDefLine 在 doWork 函数里调用,showDef 在 showDefLine 里调用了

#17 楼 @gugutian 参考最后的两块 js 代码

syl7752 回复

可以 diff 出差异 然后结算差异化覆盖率

raoweijian jacoco 增量覆盖率计算工具 中提及了此贴 07月18日 20:13

请教一下,这个 git diff 用了那个类库呀?

可以 diff 出差异 然后结算差异化覆盖率,咋实现?

justin 回复

@justin 你这边是如何统计出来的?

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册