专栏首页软件测试那些事super-jacoco源码分析与二次开发

super-jacoco源码分析与二次开发

在第一篇文章super-jacoco单元测试覆盖率度量实践-1中,笔者介绍了Super-Jacoco的单元测试覆盖率统计只要向Super-Jacoco服务发送如下的一个post请求

启动覆盖率收集
 URL:/cov/triggerUnitCover
 调用方法:POST
 参数(body方式传入):
 {"uuid":"uuid","type":1,"gitUrl":"git@git","subModule":"",
 "baseVersion":"master","nowVersion":"feature","envType":"-Ptest"}
 返回:{"code":200,"data":true,"msg":"msg"}

Super-Jacoco在接收到请求后,就会自动完成单元测试和覆盖率统计的整个过程并入库。用户可以通过结果查询接口根据事前指定的uuid来查询结果。

在本文中,笔者将结合Super-Jacoco的源码进行分析介绍上述功能是如何实现的,并结合实际项目介绍对Super-Jacoco的增量改动。

使用JGit操作Git

JGit 是一个轻量级纯Java的类库,用来实现 类似命令行的Git 版本控制。例如,以下是super-jacoco的GitHandler类中进行代码库克隆并检出指定分支的方法。

public Git cloneRepository(String gitUrl, String codePath, String commitId) throws GitAPIException {
    Git git = Git.cloneRepository()
            .setURI(gitUrl)
            .setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password))
            .setDirectory(new File(codePath))
            .setBranch(commitId)
            .call();
    // 切换到指定commitId
    checkoutBranch(git, commitId);
    return git;
}

private static Ref checkoutBranch(Git git, String branch) {
    try {
        return git.checkout()
                .setName(branch)
                .call();
    } catch (GitAPIException e) {
        throw new IllegalStateException(e);
    }
}

JGit的API还是非常流畅的,基本上不需要太多解读,熟悉Git的同学看着操作就能明白代码实现的功能。

Git diff获取差异代码

对于增量覆盖率统计来说,如何甄别出目标分支与基线分支之间的代码差异,是整个算法的基础。我们知道,在命令行中,可以通过类似如下的方式获取到两个SHA,如commitID或者branch之间的代码差异。

$ git diff SHA1 SHA2

在super-jacoco中,则需要通过JGit实现类似的功能。通过查阅源码,发现是在JDiffFiles类中实现这个功能的。核心的代码行如下:

File newF = new File(coverageReport.getNowLocalPath());
File oldF = new File(coverageReport.getBaseLocalPath());
Git newGit;
Git oldGit;
Repository newRepository;
Repository oldRepository;
newGit = Git.open(newF);
newRepository = newGit.getRepository();
oldGit = Git.open(oldF);
oldRepository = oldGit.getRepository();
ObjectId baseObjId = oldGit.getRepository().resolve(coverageReport.getBaseVersion());
ObjectId nowObjId = newGit.getRepository().resolve(coverageReport.getNowVersion());
AbstractTreeIterator newTree = prepareTreeParser(newRepository, nowObjId);
AbstractTreeIterator oldTree = prepareTreeParser(oldRepository, baseObjId);
Listdiff = newGit.diff().setOldTree(oldTree).setNewTree(newTree).setShowNameAndStatusOnly(true).call();

Super-Jacoo的开发同学通过JGit通过两次克隆代码库,作为oldRepo和newRepo,并分别切换到了基线和目标两个分支,以此作为增量覆盖率统计分析的对象,并通过上述代码中的最后一行获取到了目标分支相对于基线的差异部分,即Listdiff。

由于是做增量代码覆盖率统计,后续只要再过滤出来代码变动的部分,如新增和修改即可。删除部分由于已不存在,可以直接忽略。最后,将存在变动的各个类的相关方法保存到一个Map中返回,为后续的Jacoco分析提供源数据。

关于使用JGit操作Git的部分就简要介绍到这里了。

对Super-Jacoco的改造以适应代码库结构

场景

在单元测试覆盖率统计的场景中,Super-Jacoco使用了检出代码库后,自行编译执行单测用例的方式来获取覆盖率数据。例如,以下是来自CodeCompilerExecutor类中的一行代码,用于执行代码编译。

String[] compileCmd = new String[]{"cd " 
+ coverageReport.getNowLocalPath() 
+ "&&mvn clean compile " 
+(StringUtils.isEmpty(coverageReport.getEnvType()) 
 ? "" : "-P=" + coverageReport.getEnvType()) + ">>" + logFile};

这里,Super-Jacoco团队做了一个假设,也就是说pom.xml文件应该位于项目的根目录上。

对于纯后台类的项目,或者是一般的小型项目,这是一个比较自然的事情。不过在笔者服务的公司,一般来说一个系统的代码库由于历史原因,按照了类似的结构进行存放。假设有ProjectA,则代码库目录为:

ProjectA--01frondend
 --02backend
--app1
 --03database

假设代码库检出到目录/home/super-jacoco/projectA目录下,则app1这个java后台应用的pom.xml文件的绝对路径是:

/home/super-jacoco/projectA/02backend/app1/pom.xml

与团队假定的目录coverageReport.NowLocalPath,有一个相对的偏移。

为了能让这种代码结构的项目也能使用Super-Jacoco,笔者对其进行了一个简单的改造。

需求:

在Super-Jacoco单测时,能够适应适应项目存放pom.xml的不同位置,并正确执行该项目的编译、测试、覆盖率收集等工作。

方案分析:

通过阅读代码,笔者发现Super-Jacoco使用了CoverageReportEntity 这个类来作为整个被测项目的数据。

/**
 * @description:
 * @author: gaoweiwei_v
 * @time: 2019/7/29 2:29 PM
 */

@Data
public class CoverageReportEntity {

    private Integer id;
    private String uuid;
    private String gitUrl;
    private String baseVersion;
    private String nowVersion;
    private String nowLocalPath = "";
    private String baseLocalPath = "";
}

然后在克隆代码库的类CodeCloneExecutor中,有如下的代码,

String nowLocalPath = CODE_ROOT + uuid + "/"
 + coverageReport.getNowVersion().replace("/", "_");
coverageReport.setNowLocalPath(nowLocalPath);

这样,就通过coverageReport.getNowLocalPath来获取项目库检出之后保存在执行环境中的绝对路径,也就是假设的pom.xml所在的路径了。

此外,NowLocalPath还用于表示代码库的根目录。

综合上述分析,可以发现NowLocalPath实际上存在着两个含义,即:代码库的根目录,以及pom.xml的所在目录。

为了能应对pom.xml不在代码库根目录下的场景,考虑通过额外使用一个变量来表示代码库相对于代码库根目录的偏移,如在本文开头的案例中,后台应用的pom.xml文件的绝对路径是:

/home/super-jacoco/projectA/02backend/app1/pom.xml

假设:

coverageReport.NowLocalPath=/home/super-jacoco/projectA
coverageReport.codePath=02backend/app1

由此,即可推导出pom.xml所在的位置了。

改造实现

理清楚Super-Jacoco的工作过程之后,修改相对就比较容易了。

首先,笔者借用了CoverageReportEntity这个类中的codePath变量来表示pomx.xml相对项目根目录的偏移位置。通过搜索,发现这个变量在此处定义后在项目中并无使用,因此考虑借用。

然后,参照这nowLocalPath和baseLocalPath, 额外定义了nowLocalCodePath和baseLocalCodePath来指代Super-Jacoco将不同分支克隆到不同目录后的pom.xml所在的不同目录位置。

给目录变量赋值

在CodeCloneExecutor中,通过接口传入相关数据,并根据运行时的实际结果,赋值给上述变量

修改部分调用

对于原先使用了getNowLocalPath的方法来获取pom.xml所在位置的,则需要替换为getNowLocalCodePath。

这样,经过上述修改后,pom.xml不在项目根目录而是某个子目录中的场景,也能使用Super-Jacoco来实现覆盖率的统计了。

文章分享自微信公众号:
软件测试那些事

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:风月同天测试人
原始发表时间:2022-02-03
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • 基于Super-Jacoco的精准测试实践之路

    借助技术手段、通过辅助算法对软件测试过程进行可视化、分析及优化的过程,使得测试过程更加可视化、智能、可信和精准。

    咻咻ing
  • super-jacoco单元测试覆盖率度量实践-1

    代码覆盖率,尤其是增量代码覆盖率,是质量门禁的重要指标之一。由于一些不可名状的原因,团队原先提供质量门禁服务的工具暂时停服了,因此需要另外寻找一个工具来代替提...

    Antony
  • MBean与JMX源码分析

    JMX(java Management Exetensions)在Java编程语言中定义了应用程序以及网络管理和监控的体系结构、设计模式、应用程序接口以及服务。...

    歪歪梯
  • ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读起来没...

    lwen
  • netty详解与源码分析

    假如你新开了一家火锅店,由于前期资金比较短缺,你没有足够的资金去请店员,只有你和你老婆两个人(夫妻店),你为了让你老婆轻松一点,你让你老婆啥事不用做,只负责听顾...

    全栈程序员站长
  • AQS 与 Sync 源码分析

    ReentrantReadWriteLock 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读...

    lwen
  • AQS 与 Sync 源码分析

    ReentrantReadWriteLock 源码分析 1. 在阅读源码时做了大量的注释,并且做了一些测试分析源码内的执行流程,由于博客篇幅有限,并且代码阅读...

    lwen
  • HashTable,HashMap与ConcurrentHashMap源码分析

    Oceanlong
  • Mybatis深入源码分析之Mapper与接口绑定原理源码分析

    紧接上篇文章:Mybatis深入源码分析之SqlSessionFactoryBuilder源码分析,这里再来分析下,Mapper与接口绑定原理。

    须臾之余
  • Spring Security 架构与源码分析

    Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就...

    JadePeng
  • 花样使用Handler与源码分析

    前几天在跟公司大佬讨论一个问题时,看到他使用Handler的一种方式,旁边的同事在说:以前不是这么用的啊。这个问题引发了我的好奇,虽然当时翻清楚道理了,但是还是...

    砸漏
  • Java SPI原理与源码分析

    SPI是Service Provider Interface的缩写,jdk1.6版本开始内置的一种扩展机制,主要用于扩展框架的能力,其实就是框架定义一种能力(规...

    叔牙
  • 源码分析Dubbo NettyServer与HeaderExchangeServer

    本文主要分析一下NettyServer,HeaderExchangeServer实现细节。

    丁威
  • golang初探与命令源码分析

    就是有一个main.go的main函数里调用了另一个demo.go里的hello()函数。其中main.go和hello.go同属于main包。但是在main....

    我的小碗汤
  • java jdbc使用与源码分析

    东营浪人
  • FreeRTOS介绍与源码结构分析

    FreeRTOS,全称Free Real Time Operating System,即免费的实时操作系统。相比于计算机中用到的Windows,MacOS,Li...

    xxpcb
  • Vuex框架原理与源码分析

    Vuex是一个专为Vue服务,用于管理页面数据状态、提供统一数据操作的生态系统。它集中于MVC模式中的Model层,规定所有的数据操作必须通过 action -...

    美团技术团队
  • Service 启动与绑定源码分析

    在Activity调用startService方法实质调用的是ContextWrapper中的startService方法。

    Yif
  • 《Android 创建线程源码与OOM分析》

    | 导语 企鹅FM近几个版本的外网Crash出现很多OutOfMemory(以下简称OOM)问题,Crash的堆栈都在Thread::start方法上。该文详细...

    腾讯Bugly

扫码关注腾讯云开发者

领取腾讯云代金券