java代码实现单元测试jacoco覆盖率收集生成多模块聚合报告

it2024-05-13  48

文章目录

背景一、准备工作 - 生成exec文件二、准备工作 - 引入依赖 三、利用jenkins-jacoco插件源码收集覆盖率结果四、生成jacoco报告文件,聚合多模块附赠相关知识点


背景

持续集成项目,完善单元测试的能力 获取单元测试通过率(mvn test 获取surefire report),获取代码覆盖率(jacoco) 本次实践基于jenkins环境 以及 jenkins - jacoco插件 收集覆盖率统计结果跟生成jacoco报告文件无关,生成jacoco聚合报告文件不依赖jenkins环境,请各位有需要的看官认真查看 maven单测已完成测试,此插件方案应该适用于java语言 gradle、android(前提已生成exec文件)


一、准备工作 - 生成exec文件

maven插件的使用方式: - 在pom.xml文件里配置插件 - 使用命令行加载 pom文件配置网上很多资料,跳过了,也不是此文关注点。因为是持续集成,需要给各种项目做编译,并不只是自己的,因此不能改变用户的代码情况下,使用命令行形式为佳。 命令行形式: - maven测试命令:mvn clean test - 配置命令:-Dautoconfig.skip=true -Dmaven.test.skip=false -Dmaven.test.failure.ignore=true - maven的jacoco插件:org.jacoco:jacoco-maven-plugin:0.8.5:prepare-agent - 运行单元测试并生成exec: mvn org.jacoco:jacoco-maven-plugin:0.8.5:prepare-agent clean test -Dautoconfig.skip=true -Dmaven.test.skip=false -Dmaven.test.failure.ignore=true 附: - gradle、android的exec文件生成:在项目根目录放入一个.gradle文件,命令行加载编译 - ./gradlew --init-script init.gradle clean build 不是专业的就不细讲啦~

二、准备工作 - 引入依赖

<dependency> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.5</version> </dependency> jacoco-maven-plugin里包含了org.jacoco.core,org.jacoco.report都是必要的。 因为是在jenkins插件开发,开发中遇到过依赖冲突问题,困扰几天的奇怪的bug最终也是依赖问题,认真对待依赖很重要

三、利用jenkins-jacoco插件源码收集覆盖率结果

jacoco插件的JacocoPublisher类 perform方法为执行入口 一番分析后提取核心代码占为己有*-* 其实以下代码都是jacoco插件源码的抽取,方便自己开发插件,扩展功能,如jacoco增量覆盖率检测,自定义前端样式 写文章时候才发现很多类都是基于jenkins,普通项目不一定很好移植,本地@test可行估计也是准备了一定的环境,但是还是觉得应该能成功的 public class JacocoCore { private String rootDir = "/data/myapp"; private String execsPattern = "**/*.exec"; private String classesPattern = "**/classes"; private String sourcesPattern = "**/src/main/java"; private String inclusionPattern = ""; private String exclusionPattern = "**/*Test*.class"; private String sourceInclusionPattern = "**/*.java,**/*.groovy,**/*.kt,**/*.kts"; // 收集所有exec文件List<File> public List<FilePath> collectExecFilesByPattern(String pattern){ return Arrays.asList(workspace.list(pattern)); } public void getJacocoResultData() { 1. 设定一个收集文件用的根目录 JacocoReportDir reportDir = new JacocoReportDir(rootDir); 2. 通过通配符收集符合条件的目录路径,如app/amodule/target/classes,app/bmodule/target/classes FilePath[] machedClassDirs = workspace.act(new JacocoPublisher.ResolveDirPaths(classesPattern)) 3. 将classes目录下所有的符合条件"**/*.class"的文件全部拷贝到reportDir.getClassesDir()目录下 for (FilePath dir : matchedClassDirs) { int copied = reportDir.saveClassesFrom(dir, "**/*.class") } logger.info("copy to " + reportDir.getClassesDir().getAbsolutePath()) 4. 同样的将source文件拷贝 FilePath[] machedSrcDirs = workspace.act(new JacocoPublisher.ResolveDirPaths(sourcesPattern)) for (FilePath dir : matchedSrcDirs) { int copied = reportDir.saveSourcesFrom(dir, sourceInclusionPattern, sourceExclusionPattern) } 5. 设定分析的目标文件及排除文件 if(inclusionPattern != null) { String [] includes = inclusionPattern.split("\\s*,\\s8") } if(exclusionPattern != null) { String [] excludes = exclusionPattern.split("\\s*,\\s8") } 6. 设定扩展参数 JacocoHealthReportThresholds healthReports = new JacocoHealthReportThresholds(0,...0); 7. 构造jacoco程序 final JacocoBuildAction action = JacocoBuildAction.load(healthReports, listener, reportDir, includes, excludes); action.getThresholds().ensureValid(); 8. 获取覆盖率报告结果 final CoverageReport result = action.getResult(); logger.info("coverage of class:" + result.getClassCoverage().getPercentageFloat() + "%") ...其他指标 备注:instructionCoverage就是sonar显示的汇总后的总覆盖率指标,getPercentageFloat源码是已经乘100的 logger.info("coverage :" + new DecimalFormat("0.00%").format(result.getInstructionCoverage().getPercentageFloat() / 100)) }

四、生成jacoco报告文件,聚合多模块

生成报告文件很简单,maven jacoco插件官网就有示例,已放在参考连接 此时生成的报告是每个模块都独立的,报告首页是这样的:

那么如何将多个模块的报告都聚合到一个文件里呢? 怎么才能获得下图带模块文件夹的jacoco报告呢?

有一种常规的生成方式:新建一个空module,在pom文件里写jacoco插件配置 具体也放在参考链接啦~ 综合其他两个附件内容及一顿乱测后 终于悟出了生成jacoco聚合报告的真理!! // 主进程 public void createReport(String appName, List<File> execFiles){ JacocoReportGenerator jacocoReportGenerator = new JacocoReportGenerator(); List<IReportVisitor> visitors = new ArrayList<>(); Map<IbundleCoverage, File> coverageSourceDirMap = new HashMap<>(); File reportSaveDir = new File("/data/jacoco-reports"); for(File execFile : execFiles){ File classesDir = new File(execFile.getParent, "classes"); File sourcesDir = new File(execFile.getParentFile().getParent() + "/src/main/java"); File moduleDir = execFile.getParentFile().getParentFile(); jacocoReportGenerator.setSourceInfo(moduleDir.getName, execFile, classesDir, sourcesDir, reportSaveDir) jacocoReportGenerator.addToBundleCoverageList(coverageSourceDirMap); jacocoReportGenerator.createCoverageList(visitors); } jacocoReportGenerator.createReports(coverageSourceDirMap, visitors, appName); } /* * 报告生成器 */ public class ReportGenerator { private final String title; private final File execFile; private final File classesDirectory; private final File sourceDirectory; private final File reportDirectory; private ExecFileLoader execFileLoader; public ReportGenerator(){ } public void addToBundleCoverageList(Map<IbundleCoverage, File> coverageSourceDirMap) throws IOException { // 将exec文件的覆盖率分析加载到内存 loadExecutionData(); // 分析获取覆盖率信息 bundleCoverage = analyzeStructure(); coverageSourceDirMap.put(bundleCoverage, sourceDirectory); } public void createCoverageList(List<IReportVisitor> visitors) throws IOException { for(int i=0; i<coverageList.size(); i++) { // 创建报告生成容器 HTMLFormatter htmlFormatter = new HTMLFormatter(); IReportVisitor visitor = htmlFormatter .createVisitor(new FileMultiReportOutput(reportDirectory)); // 访问标记覆盖信息 visitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(), execFileLoader.getExecutionDataStore().getContents()); visitors.add(visitor); } } public void createReportFromList(Map<IbundleCoverage, File> coverageSourceDirMap, List<IReportVisitor> visitors, String appName) throws IOException{ MultiReportVisitor mrv = new MultiReportVisitor(visitors); IReportGroupVisitor irgv = mrv.visitGroup(appName); for(Map.Entry<iBundleCoverage, File> iBundleCoverageFileEntry : coverageSourceDirMap.entrySet()){ irgv.visitBundle(iBundleCoverageFileEntry.getKey(), new DirectorySorceFileLocator(iBundleCoverageFileEntry.getValue(), "utf-8", 4)); } mrv.visitEnd(); } private void loadExecutionData() throws IOException { execFileLoader = new ExecFileLoader(); execFileLoader.load(executionDataFile); } private IBundleCoverage analyzeStructure() throws IOException { final CoverageBuilder coverageBuilder = new CoverageBuilder(); final Analyzer analyzer = new Analyzer( execFileLoader.getExecutionDataStore(), coverageBuilder); analyzer.analyzeAll(classesDirectory); return coverageBuilder.getBundle(title); } }

附赠相关知识点

附1: 根据生成的exec文件给其所在模块生成jacoco报告(在target/site目录下) 因为想要使用java代码生成报告并将每个模块的报告合成到文件夹形式的jacoco首页形式,所以此为附加信息提供参考,无该要求的可直接命令行生成并收集报告文件即可 mvn org.jacoco:jacoco-maven-plugin:report 附2: - maven的surefire-report插件:surefire-report:report-only -DalwaysGenerateSurefireReport=false(没有测试用例的模块不生成html报告) - site配置:mvn site -DgenerateReports=false(给surefire-report生成css相关文件,由于所有的html报告都用的同一套css,所以可复用) 有样式的surefire-report应该是这样子的:


参考链接 pom文件形式生成jacoco聚合报告 官网jacoco报告生成样例 代码实现jacoco报告多模块聚合

最新回复(0)