小工具知识网

当前位置: 首页 > 行业知识 > Android增量报告生成新方案

行业知识

Android增量报告生成新方案

2026-11-11 22:37:09 来源:互联网转载

��  在开发精准测试覆盖率相关的功能时候,对于Android的增量报告,由于担心修改jacoco插件会影响App的打包,所以一直没有修改。在网上查了一下,没有找到合适的方案。只有一个diff-cover开源项目:https://github.com/abmaonline/diff-cover

经过测试,可以生成对比分支的增量报告,可是对于对比版本,就不太适应了。于是就各种调研,最后通过最低层的方案,过滤文件和函数,于是有了第一个版本的增量覆盖率报告生成方案。

一,增量覆盖率报告方案一

第一版本的增量覆盖率报告如下流程:

此方案虽然能生成增量覆盖率报告,但存在如下问题:

1,diff-cover生成的报告只能对比分支,不能对比版本;并且生成的覆盖率报告和jacoco相差很大;

2,生成的报告数据欠缺,diff-cover只有行覆盖率;而对比版本过滤的方案,生成的有行,方法和类,但没有分支数据;

3,操作比较繁琐,要过滤很多数据,如果diff文件较多,生成时间比较长;

4,最终的代码渲染没有办法按增量处理,只能过滤出diff类对应的文件,渲染则是全量的。

二,增量覆盖率报告方案二

     Android jacoco生成增量报告,早期没有找到合适的方案,后面做了一些调研,发现下面有个方案:

1,参考文档

Android 增量代码测试覆盖率工具实践:https://juejin.cn/post/6920029313316159502

2,开源项目

AndJacoco:https://github.com/ttpai/AndJacoco

此方案的核心思想是:

  • 通过指定的对比方式或是分支,或是版本,找到diff的文件及函数列表;

  • 在jacoco进行注入的时候,只对增量代码做注入;

  • 最后根据采集的覆盖率数据,生成报告,即为增量报告。

    经过各种尝试,发现这个项目在文件:org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java 中与jacoco github上的有差别::https://github.com/jacoco/jacoco/blob/master/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java

    主要是下面这个函数,修改成与github上的一样就可以,可能是这个jacoco项目比较早的原因:

    private static int getFirstGeneratedLineNumber(final String sourceFileName,      final String smap) {   try {      final BufferedReader br = new BufferedReader(            new StringReader(smap));      expectLine(br, "SMAP");      // OutputFileName      expectLine(br, sourceFileName);      // DefaultStratumId      expectLine(br, "Kotlin");      // StratumSection      expectLine(br, "*S Kotlin");      // FileSection      expectLine(br, "*F");      final BitSet sourceFileIds = new BitSet();      String line;      while (!"*L".equals(line = br.readLine())) {         // AbsoluteFileName         br.readLine();         final Matcher m = FILE_INFO_PATTERN.matcher(line);         if (!m.matches()) {            throw new IllegalStateException(                  "Unexpected SMAP line: " + line);         }         final String fileName = m.group(2);         if (fileName.equals(sourceFileName)) {            sourceFileIds.set(Integer.parseInt(m.group(1)));         }      }      if (sourceFileIds.isEmpty()) {         throw new IllegalStateException("Unexpected SMAP FileSection");      }      // LineSection      int min = Integer.MAX_VALUE;      while (true) {         line = br.readLine();         if (line.equals("*E") || line.equals("*S KotlinDebug")) {            break;         }         final Matcher m = LINE_INFO_PATTERN.matcher(line);         if (!m.matches()) {            throw new IllegalStateException(                  "Unexpected SMAP line: " + line);         }         final int inputStartLine = Integer.parseInt(m.group(1));         final int lineFileID = Integer               .parseInt(m.group(2).substring(1));         final int outputStartLine = Integer.parseInt(m.group(4));         if (sourceFileIds.get(lineFileID)               && inputStartLine == outputStartLine) {            continue;         }         min = Math.min(outputStartLine, min);      }      return min;   } catch (final IOException e) {      // Must not happen with StringReader      throw new AssertionError(e);   }}

    开源项目的函数检测也有问题,需要改成如下所示:

    CodeDiffUtil.java/******************************************************************************* * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors * This program and the accompanying materials are made available under * the terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 * * Contributors: *    Marc R. Hoffmann - initial API and implementation * *******************************************************************************/package org.jacoco.core.internal.diff;import org.jacoco.core.analysis.CoverageBuilder;import org.objectweb.asm.Type;import java.io.FileOutputStream;import java.io.PrintStream;import java.io.PrintWriter;import java.lang.reflect.Array;import java.util.Arrays;import java.util.List;import java.util.stream.Stream;/** * @ProjectName: root * @Package: org.jacoco.core.internal.diff * @Description: 差异代码处理类 * @Author: duanrui * @CreateDate: 2021/1/12 15:17 * @Version: 1.0 *           

    * Copyright: Copyright (c) 2021 */public class CodeDiffUtil { private final static String OPERATE_ADD = "ADD"; private static String OutputStream; /** * 检测类是否在差异代码中 * * @param className * @return Boolean */ public static Boolean checkClassIn(String className, List classInfos) { if (null == classInfos || classInfos.isEmpty() || null == className) { return Boolean.FALSE; } System.out.println("className="+className) // 这里要考虑匿名内部类的问题 return classInfos.stream() .anyMatch(c -> className.equals(c.getClassFile()) || className.split("\\$")[0].equals(c.getClassFile())); } /** * 检测方法是否在差异代码中 * * @param className * @param methodName * @return Boolean */ public static Boolean checkMethodIn(String className, String methodName, String desc, List classInfos) { // 参数校验 if (null == classInfos || classInfos.isEmpty() || null == methodName || null == className) { return Boolean.FALSE; } ClassInfoDto classInfoDto = classInfos.stream() .filter(c -> className.equals(c.getClassFile()) || className.split("\\$")[0].equals(c.getClassFile())) .findFirst().orElse(null); if (null == classInfoDto) { return Boolean.FALSE; } // 如果是新增类,不用匹配方法,直接运行 if (OPERATE_ADD.equals(classInfoDto.getType())) { return Boolean.TRUE; } if (null == classInfoDto.getMethodInfos() || classInfoDto.getMethodInfos().isEmpty()) { return Boolean.FALSE; } // 匹配了方法,参数也需要校验 return classInfoDto.getMethodInfos().stream().anyMatch(m -> { if (methodName.equals(m.getMethodName())) { // System.out.println("className=" + className + ",methodName=" // + methodName + ",parmas=" + desc + ",m.getParameters()=" // + m.getParameters().toString()); return checkParamsIn(m.getParameters(), desc); // lambda表示式匹配 } else if (methodName.contains("lambda$") && methodName.split("\\$")[1].equals(m.getMethodName())) { return Boolean.TRUE; } else { return Boolean.FALSE; } }); } /** * 匹配参数 * @param params * 格式:String a * @param desc * 转换后格式: java.lang.String * @return */ public static Boolean checkParamsIn(List params, String desc) { // 解析ASM获取的参数 Type[] argumentTypes = Type.getArgumentTypes(desc); // 处理一下params,直接使用list有问题 String ckparams = ""; if (params.size() == 1) { ckparams = params.get(0); ckparams = ckparams.trim(); if (ckparams.length() == 0 && argumentTypes.length == 0) { return Boolean.TRUE; } else { String[] diffParams = ckparams.split(","); // 只有参数数量完全相等才做下一次比较,Type格式:I C Ljava/lang/String; if (diffParams.length > 0 && argumentTypes.length == diffParams.length) { for (int i = 0; i

    3,构建项目

     从https://gitee.com/Dray/jacoco下载项目,修改KotlinInlineFilter.java 文件,如上面所示,执行打包命令:

    mvn clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true

    项目会下载很多依赖的包,耐心等待即可。最后命令执行完成后,发现出错了,如下所示:

    相应的jar包打包完成,没有问题。

    4,测试修改后的包

        看项目的介绍,需要使用org.jacoco.cli-0.8.7-SNAPSHOT-nodeps.jar,修改成newjacococli.jar以便进行测试。

    (1)生成正常的覆盖率报告

    执行如下命令:

    java -jar ./newjacococli.jar report ./coverage-4.1.100.66230-2023-08-11-17_48_31.ec --classfiles /Users/sxf/Downloads/jacocoincrease/build_classes_12354/61/appstoreRelease/com/yiqixie/kem/im/ui/messages/roomlist/RoomListFragment.class --sourcefiles /Users/sxf/Documents/精准测试/KimCode/kim-android/packages/kim-android/eek-features/module-kim/src/main/java/ --encoding utf-8 --html ./jacoco15

    没有指定diff信息,可以生成指定类的全量覆盖率报告:

    (2)生成diff报告信息

    通过其他的功能,拿到diff文件信息,格式如下所示:

    [  {"classFile":"com/yiqixie/kem/im/ui/messages/roomlist/RoomListFragment",   "methodInfos":[        {"methodName":"updateFolderTabLayout","parameters":["folderList: List"]},        {"methodName":"onCreate","parameters":["savedInstanceState: Bundle?"]}    ],    "type":"MODIFY"  }]

    将上面的json转换成String,执行如下命令生成增量报告:

    java -jar ./newjacococli.jar report ./coverage-4.1.100.66230-2023-08-11-17_48_31.ec --classfiles /Users/sxf/Downloads/jacocoincrease/build_classes_12354/61/appstoreRelease/com/yiqixie/kem/im/ui/messages/roomlist/RoomListFragment.class --sourcefiles /Users/sxf/Documents/精准测试/KimCode/kim-android/packages/kim-android/eek-features/module-kim/src/main/java/ --encoding utf-8 --html ./jacoco16 --diffCode "[{\"classFile\":\"com/yiqixie/kem/im/ui/messages/roomlist/RoomListFragment\",\"methodInfos\":[{\"methodName\":\"updateFolderTabLayout\",\"parameters\":[\"folderList: List\"]},{\"methodName\":\"onCreate\",\"parameters\":[\"savedInstanceState: Bundle?\"]}],\"type\":\"MODIFY\"}]"

    生成的增量报告结果如下:

    渲染结果如下:

    通过上面的测试,可以达到想要的效果,现在就需要再修改一下Android agent先找到diff的信息,再去执行新的增量覆盖率的命令。

    (3) 如果diff文件过多,则无法使用命令行方式

    建议改成以json文件传递。

    java -jar ./newjacococli.jar report ./packages/kim-android/app/build/outputs/code-coverage/connected/mergedcoverage.ec  --classfiles ./build_classes_12397/62/appstoreRelease/com/yiqixie/kem/im/ui/messages/media/MediaFragment.class --classfiles ./build_classes_12397/62/appstoreRelease/com/yiqixie/kem/im/ui/messages/media/MediaLayout.class --classfiles ./build_classes_12397/62/appstoreRelease/com/yiqixie/kem/im/ui/messages/media/MediaSlideActivity.class --sourcefiles ./packages/kim-android/eek-features/module-kim/src/main/java --sourcefiles ./packages/kim-android/eek-features/module-kim/src/main/java --sourcefiles ./packages/kim-android/eek-features/module-kim/src/main/java --encoding utf-8 --html ./diffreportsxf --diffCodeFiles ./diff_files.json

    5,最新的生成覆盖率报告流程

        通过借助于上面修改后的jacococli.jar包,最新生成增量报告的流程如下所示:

    新的生成增量报告的方案做到了如下几点:

    • 统一对比版本和对比分支生成增量报告的逻辑,最终生成jacoco格式的报告;

    • 补全了所有增量报告数据,分支,行,函数和类的覆盖率数据都是准确的;

    • 增量方案渲染代码页覆盖情况,方便测试同学根据增量函数进行排查问题。

    • 无需修改jacoco插件,不影响覆盖率数据的采集。

上一篇:如何在服务器无视服主?

下一篇:像素工厂如何进入服务器?