news 2026/4/2 19:07:31

使用jacoco的API方式解析覆盖率和生成覆盖率报告

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
使用jacoco的API方式解析覆盖率和生成覆盖率报告

JaCoCo 离线解析.exec文件:生成与获取详细覆盖率数据全指南

一、概述

在JaCoCo覆盖率统计场景中,jacoco支持cli通过命令行的方式生成覆盖率报告,但是这样无法很好和其他系统集成,本文演示通过jacoco提供的API的方式生成覆盖率报告和获取覆盖率的具体详情数据。

二、通过调用API的方式解析.exec文件获取详细覆盖率数据

生成.exec文件后,通过JaCoCo API的ExecFileLoader加载文件,结合AnalyzerCoverageBuilder解析原始字节码,可获取类、方法、行、分支级别的详细覆盖率数据。以下是完整实现方案。

3.1 核心依赖确认

确保项目已引入解析所需的核心依赖(若已引入可跳过):

<!-- JaCoCo 核心依赖(解析基础) --> <dependency> <groupId>org.jacoco</groupId> <artifactId>jacoco-core</artifactId> <version>0.8.11</version> </dependency> <!-- JaCoCo 报告依赖(提供ExecFileLoader等解析工具) --> <dependency> <groupId>org.jacoco</groupId> <artifactId>jacoco-report</artifactId> <version>0.8.11</version> </dependency>

3.2 完整解析代码实现

以下代码实现了“加载.exec文件 → 解析指定类/全量类 → 提取详细覆盖率数据”的完整流程,包含类、方法、行、分支级数据的获取。

import org.jacoco.core.analysis.Analyzer; import org.jacoco.core.analysis.CoverageBuilder; import org.jacoco.core.analysis.IClassCoverage; import org.jacoco.core.analysis.IMethodCoverage; import org.jacoco.core.analysis.ILineCoverage; import org.jacoco.core.analysis.IBranchCoverage; import org.jacoco.core.data.ExecFileLoader; import org.jacoco.core.data.ExecutionDataStore; import org.jacoco.core.data.SessionInfoStore; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Collection; public class JacocoExecParser { // 核心方法:解析.exec文件,获取指定类的详细覆盖率数据 public static void parseExecForSingleClass(String execPath, Class<?> targetClass) throws IOException { // 1. 加载.exec文件 ExecFileLoader execFileLoader = loadExecFile(execPath); // 2. 提取执行数据和会话信息 ExecutionDataStore executionDataStore = execFileLoader.getExecutionDataStore(); SessionInfoStore sessionInfoStore = execFileLoader.getSessionInfoStore(); // 3. 打印会话信息(可选,用于日志跟踪) printSessionInfo(sessionInfoStore.getInfos()); // 4. 创建CoverageBuilder(用于构建覆盖率数据)和Analyzer(解析器) CoverageBuilder coverageBuilder = new CoverageBuilder(); Analyzer analyzer = new Analyzer(executionDataStore, coverageBuilder); // 5. 解析目标类的原始字节码(Analyzer会自动匹配.exec中的执行数据) byte[] originalBytes = getClassBytes(targetClass); analyzer.analyzeClass(new ByteArrayInputStream(originalBytes), targetClass.getName()); // 6. 提取并打印详细覆盖率数据 extractAndPrintCoverageData(coverageBuilder.getClasses()); } // 扩展方法:解析.exec文件,获取指定目录下所有类的覆盖率数据(全量解析) public static void parseExecForAllClasses(String execPath, String classDirPath) throws IOException { // 1. 加载.exec文件 ExecFileLoader execFileLoader = loadExecFile(execPath); // 2. 创建解析器 CoverageBuilder coverageBuilder = new CoverageBuilder(); Analyzer analyzer = new Analyzer(execFileLoader.getExecutionDataStore(), coverageBuilder); // 3. 批量解析指定目录下的所有.class文件(如target/classes) File classDir = new File(classDirPath); if (!classDir.exists() || !classDir.isDirectory()) { throw new IllegalArgumentException("无效的类文件目录:" + classDirPath); } analyzer.analyzeAll(classDir); // 4. 提取并打印全量覆盖率数据 extractAndPrintCoverageData(coverageBuilder.getClasses()); } // 辅助方法:加载.exec文件 private static ExecFileLoader loadExecFile(String execPath) throws IOException { ExecFileLoader execFileLoader = new ExecFileLoader(); File execFile = new File(execPath); if (!execFile.exists()) { throw new IOException(".exec文件不存在:" + execPath); } execFileLoader.load(execFile); System.out.println(".exec文件加载成功:" + execPath); return execFileLoader; } // 辅助方法:打印会话信息 private static void printSessionInfo(Collection<SessionInfo> sessionInfos) { System.out.println("\n=== 会话信息 ==="); for (SessionInfo sessionInfo : sessionInfos) { System.out.println("会话ID:" + sessionInfo.getId()); System.out.println("开始时间:" + sessionInfo.getStartTime()); System.out.println("结束时间:" + sessionInfo.getDumpTime()); } } // 辅助方法:从类路径读取类的字节码 private static byte[] getClassBytes(Class<?> clazz) throws IOException { String className = clazz.getName().replace('.', '/') + ".class"; try (InputStream is = clazz.getClassLoader().getResourceAsStream(className)) { if (is == null) { throw new IOException("Class file not found: " + className); } return is.readAllBytes(); } } // 核心方法:提取并打印类、方法、行、分支级覆盖率数据 private static void extractAndPrintCoverageData(Collection<IClassCoverage> classCoverages) { System.out.println("\n=== 详细覆盖率数据 ==="); for (IClassCoverage classCoverage : classCoverages) { // 1. 类级覆盖率数据 System.out.println("\n【类信息】"); System.out.println("类名:" + classCoverage.getName()); System.out.println("类全限定名:" + classCoverage.getPackageName() + "." + classCoverage.getName()); System.out.println("类行覆盖率:" + String.format("%.2f%%", classCoverage.getLineCoverage() * 100)); System.out.println("类分支覆盖率:" + String.format("%.2f%%", classCoverage.getBranchCoverage() * 100)); System.out.println("类方法覆盖率:" + String.format("%.2f%%", classCoverage.getMethodCoverage() * 100)); System.out.println("覆盖的行数量:" + classCoverage.getCoveredLines()); System.out.println("总行了数量:" + classCoverage.getLines()); // 2. 方法级覆盖率数据(遍历类中所有方法) for (IMethodCoverage methodCoverage : classCoverage.getMethods()) { System.out.println("\n 【方法信息】"); System.out.println(" 方法名:" + methodCoverage.getName()); System.out.println(" 方法描述符(参数+返回值类型):" + methodCoverage.getDesc()); System.out.println(" 方法行覆盖率:" + String.format("%.2f%%", methodCoverage.getLineCoverage() * 100)); System.out.println(" 方法分支覆盖率:" + String.format("%.2f%%", methodCoverage.getBranchCoverage() * 100)); System.out.println(" 方法开始行号:" + methodCoverage.getFirstLine()); System.out.println(" 方法结束行号:" + methodCoverage.getLastLine()); // 3. 行级覆盖率数据(遍历方法中所有行) for (int lineNum = methodCoverage.getFirstLine(); lineNum <= methodCoverage.getLastLine(); lineNum++) { ILineCoverage lineCoverage = methodCoverage.getLine(lineNum); if (lineCoverage != null) { String lineStatus = getLineStatusDesc(lineCoverage.getStatus()); System.out.println("\n 行号 " + lineNum + ":" + lineStatus); // 4. 分支级覆盖率数据(仅分支行有分支信息) if (lineCoverage.getBranchCoverage() != null) { IBranchCoverage branchCoverage = lineCoverage.getBranchCoverage(); System.out.println(" 分支覆盖数量:" + branchCoverage.getCoveredBranches()); System.out.println(" 分支总数量:" + branchCoverage.getTotalBranches()); System.out.println(" 分支覆盖率:" + String.format("%.2f%%", branchCoverage.getCoverageRatio() * 100)); } } } } } } // 辅助方法:将行覆盖状态码转换为描述文本 private static String getLineStatusDesc(int status) { return switch (status) { case 0 -> "未覆盖(该行代码未执行)"; case 1 -> "部分覆盖(分支行,仅部分分支执行)"; case 2 -> "完全覆盖(该行代码/所有分支均执行)"; default -> "未知状态"; }; } // 测试:解析指定类的覆盖率数据 public static void main(String[] args) throws IOException { // 1. 解析单个类 parseExecForSingleClass("target/jacoco.exec", com.example.TargetService.class); // 2. 解析全量类(注释掉上面,打开下面测试) // parseExecForAllClasses("target/jacoco.exec", "target/classes"); } }

3.3 核心API说明

  • ExecFileLoader:核心加载工具,通过load(File)方法读取.exec文件,内部包含ExecutionDataStore(执行数据)和SessionInfoStore(会话信息)。

  • Analyzer:解析器,核心作用是将“执行数据”与“原始字节码”关联,生成覆盖率数据。analyzeClass()解析单个类,analyzeAll()批量解析目录下所有类。

  • CoverageBuilder:覆盖率数据构建器,解析完成后,通过getClasses()获取所有解析后的类覆盖率数据(IClassCoverage)。

  • IClassCoverage/IMethodCoverage/ILineCoverage/IBranchCoverage:覆盖率数据接口,分别对应类、方法、行、分支级数据,提供获取覆盖率百分比、覆盖数量、总数量等方法。

3.4 输出示例

执行上述测试代码后,会输出类似以下的详细覆盖率数据:

.exec文件加载成功:target/jacoco.exec === 会话信息 === 会话ID:default 开始时间:1735689600000 结束时间:1735689660000 === 详细覆盖率数据 === 【类信息】 类名:TargetService 类全限定名:com.example.TargetService 类行覆盖率:85.71% 类分支覆盖率:75.00% 类方法覆盖率:100.00% 覆盖的行数量:12 总行了数量:14 【方法信息】 方法名:doBusiness 方法描述符(参数+返回值类型):(Ljava/lang/String;)Ljava/lang/String; 方法行覆盖率:83.33% 方法分支覆盖率:66.67% 方法开始行号:10 方法结束行号:25 行号 10:完全覆盖(该行代码/所有分支均执行) 行号 11:完全覆盖(该行代码/所有分支均执行) 行号 12:部分覆盖(分支行,仅部分分支执行) 分支覆盖数量:2 分支总数量:3 分支覆盖率:66.67% 行号 13:未覆盖(该行代码未执行) 行号 14:完全覆盖(该行代码/所有分支均执行) ...
import org.jacoco.core.analysis.Analyzer; import org.jacoco.core.analysis.CoverageBuilder; import org.jacoco.core.analysis.IClassCoverage; import org.jacoco.core.analysis.IMethodCoverage; import org.jacoco.core.data.ExecFileLoader; import org.jacoco.core.data.ExecutionDataStore; import org.jacoco.core.data.SessionInfoStore; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class JacocoCoverageSerializer { // 定义覆盖率数据DTO(用于序列化,避免直接序列化JaCoCo原生接口) public static class CoverageDTO { // 会话信息 private SessionDTO session; // 类覆盖率列表 private List<ClassCoverageDTO> classCoverages; // getter/setter public SessionDTO getSession() { return session; } public void setSession(SessionDTO session) { this.session = session; } public List<ClassCoverageDTO> getClassCoverages() { return classCoverages; } public void setClassCoverages(List<ClassCoverageDTO> classCoverages) { this.classCoverages = classCoverages; } } public static class SessionDTO { private String sessionId; private long startTime; private long endTime; // getter/setter public String getSessionId() { return sessionId; } public void setSessionId(String sessionId) { this.sessionId = sessionId; } public long getStartTime() { return startTime; } public void setStartTime(long startTime) { this.startTime = startTime; } public long getEndTime() { return endTime; } public void setEndTime(long endTime) { this.endTime = endTime; } } public static class ClassCoverageDTO { private String className; private String fullClassName; private double lineCoverage; private double branchCoverage; private double methodCoverage; private int coveredLines; private int totalLines; private List<MethodCoverageDTO> methodCoverages; // getter/setter public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getFullClassName() { return fullClassName; } public void setFullClassName(String fullClassName) { this.fullClassName = fullClassName; } public double getLineCoverage() { return lineCoverage; } public void setLineCoverage(double lineCoverage) { this.lineCoverage = lineCoverage; } public double getBranchCoverage() { return branchCoverage; } public void setBranchCoverage(double branchCoverage) { this.branchCoverage = branchCoverage; } public double getMethodCoverage() { return methodCoverage; } public void setMethodCoverage(double methodCoverage) { this.methodCoverage = methodCoverage; } public int getCoveredLines() { return coveredLines; } public void setCoveredLines(int coveredLines) { this.coveredLines = coveredLines; } public int getTotalLines() { return totalLines; } public void setTotalLines(int totalLines) { this.totalLines = totalLines; } public List<MethodCoverageDTO> getMethodCoverages() { return methodCoverages; } public void setMethodCoverages(List<MethodCoverageDTO> methodCoverages) { this.methodCoverages = methodCoverages; } } public static class MethodCoverageDTO { private String methodName; private String methodDesc; private double lineCoverage; private double branchCoverage; private int firstLine; private int lastLine; private List<LineCoverageDTO> lineCoverages; // getter/setter public String getMethodName() { return methodName; } public void setMethodName(String methodName) { this.methodName = methodName; } public String getMethodDesc() { return methodDesc; } public void setMethodDesc(String methodDesc) { this.methodDesc = methodDesc; } public double getLineCoverage() { return lineCoverage; } public void setLineCoverage(double lineCoverage) { this.lineCoverage = lineCoverage; } public double getBranchCoverage() { return branchCoverage; } public void setBranchCoverage(double branchCoverage) { this.branchCoverage = branchCoverage; } public int getFirstLine() { return firstLine; } public void setFirstLine(int firstLine) { this.firstLine = firstLine; } public int getLastLine() { return lastLine; } public void setLastLine(int lastLine) { this.lastLine = lastLine; } public List<LineCoverageDTO> getLineCoverages() { return lineCoverages; } public void setLineCoverages(List<LineCoverageDTO> lineCoverages) { this.lineCoverages = lineCoverages; } } public static class LineCoverageDTO { private int lineNum; private String lineStatus; private BranchCoverageDTO branchCoverage; // getter/setter public int getLineNum() { return lineNum; } public void setLineNum(int lineNum) { this.lineNum = lineNum; } public String getLineStatus() { return lineStatus; } public void setLineStatus(String lineStatus) { this.lineStatus = lineStatus; } public BranchCoverageDTO getBranchCoverage() { return branchCoverage; } public void setBranchCoverage(BranchCoverageDTO branchCoverage) { this.branchCoverage = branchCoverage; } } public static class BranchCoverageDTO { private int coveredBranches; private int totalBranches; private double branchCoverageRatio; // getter/setter public int getCoveredBranches() { return coveredBranches; } public void setCoveredBranches(int coveredBranches) { this.coveredBranches = coveredBranches; } public int getTotalBranches() { return totalBranches; } public void setTotalBranches(int totalBranches) { this.totalBranches = totalBranches; } public double getBranchCoverageRatio() { return branchCoverageRatio; } public void setBranchCoverageRatio(double branchCoverageRatio) { this.branchCoverageRatio = branchCoverageRatio; } } // 核心方法:解析.exec文件并将覆盖率数据序列化为JSON文件 public static void serializeToJson(String execPath, String classDirPath, String jsonOutputPath) throws IOException { // 1. 解析.exec文件,获取全量类覆盖率数据 CoverageDTO coverageDTO = parseExecToCoverageDTO(execPath, classDirPath); // 2. 使用FastJSON序列化(需引入fastjson依赖) String jsonStr = JSON.toJSONString(coverageDTO, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue); // 3. 写入JSON文件 try (FileWriter writer = new FileWriter(jsonOutputPath)) { writer.write(jsonStr); System.out.println("JSON序列化完成,文件路径:" + jsonOutputPath); } } // 核心方法:解析.exec文件并将覆盖率数据序列化为XML文件 public static void serializeToXml(String execPath, String classDirPath, String xmlOutputPath) throws IOException, JAXBException { // 1. 解析.exec文件,获取全量类覆盖率数据 CoverageDTO coverageDTO = parseExecToCoverageDTO(execPath, classDirPath); // 2. 使用JAXB序列化(JDK自带,无需额外依赖) JAXBContext jaxbContext = JAXBContext.newInstance(CoverageDTO.class); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); // 格式化输出 marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8"); // 编码 // 3. 写入XML文件 marshaller.marshal(coverageDTO, new File(xmlOutputPath)); System.out.println("XML序列化完成,文件路径:" + xmlOutputPath); // 可选:打印XML字符串 StringWriter stringWriter = new StringWriter(); marshaller.marshal(coverageDTO, stringWriter); System.out.println("XML内容:\n" + stringWriter.toString()); } // 辅助方法:解析.exec文件,转换为自定义CoverageDTO(核心数据转换) private static CoverageDTO parseExecToCoverageDTO(String execPath, String classDirPath) throws IOException { // 1. 加载并解析.exec文件 ExecFileLoader execFileLoader = new ExecFileLoader(); execFileLoader.load(new File(execPath)); CoverageBuilder coverageBuilder = new CoverageBuilder(); Analyzer analyzer = new Analyzer(execFileLoader.getExecutionDataStore(), coverageBuilder); analyzer.analyzeAll(new File(classDirPath)); // 2. 转换为CoverageDTO CoverageDTO coverageDTO = new CoverageDTO(); // 2.1 转换会话信息 SessionInfoStore sessionInfoStore = execFileLoader.getSessionInfoStore(); if (!sessionInfoStore.getInfos().isEmpty()) { SessionDTO sessionDTO = new SessionDTO(); var sessionInfo = sessionInfoStore.getInfos().iterator().next(); sessionDTO.setSessionId(sessionInfo.getId()); sessionDTO.setStartTime(sessionInfo.getStartTime()); sessionDTO.setEndTime(sessionInfo.getDumpTime()); coverageDTO.setSession(sessionDTO); } // 2.2 转换类覆盖率数据 Collection<IClassCoverage> classCoverages = coverageBuilder.getClasses(); List<ClassCoverageDTO> classCoverageDTOList = new ArrayList<>(); for (IClassCoverage classCoverage : classCoverages) { ClassCoverageDTO classDTO = new ClassCoverageDTO(); classDTO.setClassName(classCoverage.getName()); classDTO.setFullClassName(classCoverage.getPackageName() + "." + classCoverage.getName()); classDTO.setLineCoverage(Math.round(classCoverage.getLineCoverage() * 10000) / 100.0); // 保留两位小数 classDTO.setBranchCoverage(Math.round(classCoverage.getBranchCoverage() * 10000) / 100.0); classDTO.setMethodCoverage(Math.round(classCoverage.getMethodCoverage() * 10000) / 100.0); classDTO.setCoveredLines(classCoverage.getCoveredLines()); classDTO.setTotalLines(classCoverage.getLines()); // 转换方法覆盖率数据 List<MethodCoverageDTO> methodDTOList = new ArrayList<>(); for (IMethodCoverage methodCoverage : classCoverage.getMethods()) { MethodCoverageDTO methodDTO = new MethodCoverageDTO(); methodDTO.setMethodName(methodCoverage.getName()); methodDTO.setMethodDesc(methodCoverage.getDesc()); methodDTO.setLineCoverage(Math.round(methodCoverage.getLineCoverage() * 10000) / 100.0); methodDTO.setBranchCoverage(Math.round(methodCoverage.getBranchCoverage() * 10000) / 100.0); methodDTO.setFirstLine(methodCoverage.getFirstLine()); methodDTO.setLastLine(methodCoverage.getLastLine()); // 转换行覆盖率数据 List<LineCoverageDTO> lineDTOList = new ArrayList<>(); for (int lineNum = methodCoverage.getFirstLine(); lineNum <= methodCoverage.getLastLine(); lineNum++) { var lineCoverage = methodCoverage.getLine(lineNum); if (lineCoverage != null) { LineCoverageDTO lineDTO = new LineCoverageDTO(); lineDTO.setLineNum(lineNum); lineDTO.setLineStatus(getLineStatusDesc(lineCoverage.getStatus())); // 转换分支覆盖率数据(仅分支行) if (lineCoverage.getBranchCoverage() != null) { var branchCoverage = lineCoverage.getBranchCoverage(); BranchCoverageDTO branchDTO = new BranchCoverageDTO(); branchDTO.setCoveredBranches(branchCoverage.getCoveredBranches()); branchDTO.setTotalBranches(branchCoverage.getTotalBranches()); branchDTO.setBranchCoverageRatio(Math.round(branchCoverage.getCoverageRatio() * 10000) / 100.0); lineDTO.setBranchCoverage(branchDTO); } lineDTOList.add(lineDTO); } } methodDTO.setLineCoverages(lineDTOList); methodDTOList.add(methodDTO); } classDTO.setMethodCoverages(methodDTOList); classCoverageDTOList.add(classDTO); } coverageDTO.setClassCoverages(classCoverageDTOList); return coverageDTO; } // 辅助方法:行状态码转描述 private static String getLineStatusDesc(int status) { return switch (status) { case 0 -> "未覆盖"; case 1 -> "部分覆盖"; case 2 -> "完全覆盖"; default -> "未知"; }; } // 测试:序列化覆盖率数据 public static void main(String[] args) throws IOException, JAXBException { // 依赖说明:JSON序列化需引入fastjson依赖 // <dependency> // <groupId>com.alibaba</groupId> // <artifactId>fastjson</artifactId> // <version>2.0.44</version> // </dependency> // 1. 序列化为JSON serializeToJson("target/jacoco.exec", "target/classes", "target/coverage-report.json"); // 2. 序列化为XML(注释掉上面,打开下面测试) // serializeToXml("target/jacoco.exec", "target/classes", "target/coverage-report.xml"); } }

3.5

import org.jacoco.report.*; import org.jacoco.report.html.HTMLFormatter; import java.io.*; import java.util.*; public class HtmlReportGenerator { /** * 生成HTML格式的覆盖率报告 */ public static void generateHtmlReport( ExecutionDataStore executionData, File classesDirectory, File sourceDirectory, File reportDirectory) throws IOException { // 1. 创建报告生成器 final CoverageBuilder coverageBuilder = new CoverageBuilder(); final Analyzer analyzer = new Analyzer(executionData, coverageBuilder); // 2. 分析所有类文件 analyzeAllClasses(analyzer, classesDirectory); // 3. 创建HTML格式化器 HTMLFormatter htmlFormatter = new HTMLFormatter(); IReportVisitor visitor = htmlFormatter.createVisitor( new FileMultiReportOutput(reportDirectory)); // 4. 设置报告信息 visitor.visitInfo( executionData.getContents(), executionData.getSessionInfo() ); // 5. 生成报告 visitor.visitBundle( coverageBuilder.getBundle("MyApplication"), new DirectorySourceFileLocator( sourceDirectory, "UTF-8", 4 ) ); visitor.visitEnd(); } /** * 递归分析所有类文件 */ private static void analyzeAllClasses( Analyzer analyzer, File directory) throws IOException { for (File file : directory.listFiles()) { if (file.isDirectory()) { analyzeAllClasses(analyzer, file); } else if (file.getName().endsWith(".class")) { analyzer.analyzeAll(file); } } } /** * 生成详细的类级别报告 */ public static void generateClassReport( ExecutionDataStore data, File classFile, String className, File outputFile) throws IOException { // 分析单个类 CoverageBuilder builder = new CoverageBuilder(); Analyzer analyzer = new Analyzer(data, builder); analyzer.analyzeClass(new FileInputStream(classFile), className); // 获取类覆盖率 IClassCoverage classCoverage = builder.getClasses().stream() .filter(c -> c.getName().equals(className)) .findFirst() .orElse(null); if (classCoverage != null) { // 生成类级别的详细报告 generateClassDetailsReport(classCoverage, outputFile); } } private static void generateClassDetailsReport( IClassCoverage coverage, File outputFile) throws IOException { try (PrintWriter writer = new PrintWriter(outputFile)) { writer.println("Class: " + coverage.getName()); writer.println("=================================="); writer.printf("Line Coverage: %.2f%%\n", coverage.getLineCounter().getCoveredRatio() * 100); writer.printf("Branch Coverage: %.2f%%\n", coverage.getBranchCounter().getCoveredRatio() * 100); writer.printf("Method Coverage: %.2f%%\n", coverage.getMethodCounter().getCoveredRatio() * 100); writer.println("\nLine Details:"); writer.println("Line | Status | Missed Branches"); writer.println("-----|--------|----------------"); for (int i = coverage.getFirstLine(); i <= coverage.getLastLine(); i++) { int status = coverage.getLine(i).getStatus(); String statusStr = getStatusString(status); writer.printf("%4d | %6s | %d\n", i, statusStr, coverage.getLine(i).getBranchCounter().getMissedCount()); } } } private static String getStatusString(int status) { switch (status) { case ICounter.NOT_COVERED: return "MISSED"; case ICounter.PARTLY_COVERED: return "PARTIAL"; case ICounter.FULLY_COVERED: return "COVERED"; default: return "EMPTY"; } } } - ## 4.2 序列化关键说明 - 自定义DTO:由于JaCoCo原生接口(如`IClassCoverage`)是接口类型,且部分方法不支持序列化,因此定义了一套与原生数据对应的DTO类(如`CoverageDTO`、`ClassCoverageDTO`),用于数据转换和序列化。 - JSON序列化:使用FastJSON实现,支持格式化输出、空值保留等特性,需引入FastJSON依赖。 - XML序列化:使用JDK自带的JAXB实现,无需额外依赖,通过`Marshaller`将DTO对象转换为XML格式并写入文件。 - 数据精度:对覆盖率百分比进行四舍五入处理,保留两位小数,便于阅读。 ## 4.3 序列化输出示例(JSON) ```Plain Text { "session": { "sessionId": "default", "startTime": 1735689600000, "endTime": 1735689660000 }, "classCoverages": [ { "className": "TargetService", "fullClassName": "com.example.TargetService", "lineCoverage": 85.71, "branchCoverage": 75.00, "methodCoverage": 100.00, "coveredLines": 12, "totalLines": 14, "methodCoverages": [ { "methodName": "doBusiness", "methodDesc": "(Ljava/lang/String;)Ljava/lang/String;", "lineCoverage": 83.33, "branchCoverage": 66.67, "firstLine": 10, "lastLine": 25, "lineCoverages": [ { "lineNum": 10, "lineStatus": "完全覆盖", "branchCoverage": null }, { "lineNum": 12, "lineStatus": "部分覆盖", "branchCoverage": { "coveredBranches": 2, "totalBranches": 3, "branchCoverageRatio": 66.67 } } ] } ] } ] }
  1. 字节码一致性:解析时使用的原始字节码(.class文件)必须与生成.exec文件时的字节码完全一致(即同一版本的代码),否则会导致解析失败或覆盖率数据不准确。

  2. 依赖版本匹配:JaCoCo API版本需与生成.exec文件的JaCoCo插件版本一致(如均使用0.8.11),避免因版本差异导致的文件格式不兼容。

  3. 类加载路径:解析单个类时,需确保目标类的字节码可通过类加载器获取;批量解析时,指定的类目录(如target/classes)需正确,且包含所有需要解析的.class文件。

  4. 序列化兼容性:若需长期存储序列化后的覆盖率数据,建议使用稳定的序列化框架(如FastJSON、JAXB),并固定DTO类结构,避免后续版本变更导致反序列化失败。

  5. 性能优化:批量解析大量类时,建议分批次解析,避免一次性加载过多字节码导致内存溢出;序列化大体积覆盖率数据时,可采用流式写入(如FileWriter),减少内存占用。

六、参考资料

  • JaCoCo 官方API文档:https://www.jacoco.org/jacoco/trunk/doc/api/

  • JaCoCo 官方.exec文件格式说明:https://www.jacoco.org/jacoco/trunk/doc/execution-data.html

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/2 23:21:42

[INTERCONNECT] 直波导时域频域增益仿真对比

[INTERCONNECT] 直波导时域频域增益仿真对比 引言 正文 初始仿真 仿真优化 Author: JiJi \textrm{Author: JiJi} Author: JiJi Created Time: 2025.12.30 \textrm{Created Time: 2025.12.30} Created Time: 2025.12.30

作者头像 李华
网站建设 2026/4/1 18:19:48

Miniconda-Python3.10环境下安装Accelerate简化分布式训练

Miniconda-Python3.10环境下安装Accelerate简化分布式训练 在当今大模型&#xff08;LLM&#xff09;蓬勃发展的时代&#xff0c;动辄上百亿参数的神经网络早已无法在单张GPU上完成训练。面对算力需求的指数级增长&#xff0c;分布式训练不再是“可选项”&#xff0c;而是工程落…

作者头像 李华
网站建设 2026/3/27 2:14:16

Miniconda-Python3.10镜像在法律文书生成大模型中的应用

Miniconda-Python3.10镜像在法律文书生成大模型中的应用 在智能司法系统逐步落地的今天&#xff0c;一个看似微不足道的技术选择——开发环境配置&#xff0c;正在悄然影响着法律AI模型的可靠性与可审计性。你是否曾遇到过这样的场景&#xff1a;本地调试完美的法律文书生成模型…

作者头像 李华
网站建设 2026/4/3 3:02:54

无需重装系统:Miniconda-Python3.10镜像秒配CUDA与cuDNN版本

无需重装系统&#xff1a;Miniconda-Python3.10镜像秒配CUDA与cuDNN版本 在人工智能实验室的深夜&#xff0c;你是否经历过这样的场景&#xff1a;为了复现一篇论文&#xff0c;花了一整天时间配置环境&#xff0c;却卡在 libcudart.so 找不到&#xff1b;或者团队成员跑来问“…

作者头像 李华
网站建设 2026/4/1 19:51:42

Miniconda-Python3.10环境下安装DeepSpeed进行超大模型训练

Miniconda-Python3.10环境下安装DeepSpeed进行超大模型训练 在当前大语言模型&#xff08;LLM&#xff09;快速演进的背景下&#xff0c;百亿、千亿参数级模型已成为研究与应用的常态。然而&#xff0c;这样的庞然大物对显存和计算资源提出了极高要求——单卡训练几乎不可能实现…

作者头像 李华
网站建设 2026/3/31 16:23:59

espidf打造可扩展智能家居中枢:深度剖析

用 ESP-IDF 打造真正可扩展的智能家居中枢&#xff1a;从底层机制到实战设计智能家居的“大脑”困局我们正处在一个设备爆炸的时代。家里的灯、插座、门锁、温湿度计、摄像头&#xff0c;甚至窗帘和冰箱&#xff0c;都开始联网。但问题也随之而来&#xff1a;这些设备来自不同品…

作者头像 李华