第2.2节 Android Jacoco插件覆盖率采集

news2025/3/24 8:21:30
        JaCoCo(Java Code Coverage)是一款开源的代码覆盖率分析工具,适用于Java和Android项目。它通过插桩技术统计测试过程中代码的执行情况,生成可视化报告,帮助开发者评估测试用例的有效性。在github上开源的项目: GitHub - jacoco/jacoco: :microscope: Java Code Coverage Library ,是针对服务端的,而移动端的jacoco插件暂时没有开源,可以参考: The JaCoCo Plugin ,使用最多的版本是0.8.7,你也可以尝试使用最新版本。

2.2.1 Jacoco插件的接入

      将要接入Jacoco插件的一个Android应用,或是从github上下载一个Demo来进行测试,不过网上的Demo可能因为gradle或是其他包的版本不兼容最新的版本,需要先进行处理一下,能打包后再进行接入jacoco插件。现在我以一个简单的Android计算器的Demo做一个jacoco接入的演示,早期github上的项目地址是 https://github.com/FlamingJay/AndroidCalculator.git,后来被删除了,后面我将上传到我的github上供大家学习。
  • build.gradle中添加jacoco插件
在app下的build.gradle文件中添加对jacoco的引用,如下所示:
plugins {
    id 'com.android.application'
    id 'jacoco'
}

jacoco {
    toolVersion = "0.8.7" // 选择合适的版本
}

注意:此处使用的是0.8.7版本,这个版本比较稳定,你也可以使用最新版本。

  • 打开覆盖率采集开关
  • android {
        ...
        buildTypes {
            release {
                minifyEnabled false
                testCoverageEnabled = true
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
            debug {
                testCoverageEnabled = true
            }
        }
        ...
        }
  • 一般在debug包下进行覆盖率的测试,打开testCoverageEnabled = true, 构建项目的时候就能对代码进行插桩,采集覆盖率数据。
  • release包有代码混淆,覆盖率报告渲染的时候,无法正确对应到类的源码,所以要对release包进行测试时,需要关掉代码混淆。
     通过上面的配置,打包后的App就可以采集覆盖率数据,记录用户的具体操作覆盖。注意:此时的覆盖率数据存在于内存中,要想拿到覆盖率数据,必须人为地将覆盖率数据写入到文件中。

2.2.2 覆盖率数据采集

      由于覆盖率数据内容存在于手机内存中,当App退出后,内存中的数据将被清空。而我们要进行覆盖率测试的时候,必须要拿到覆盖率数据文件,下面我们将借助于jacoco将覆盖率数据从内容写入到文件中,代码如下:
package com.example.calculator.utils;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

public class GenerateECFile {
    public static String TAG = "GenerateECFile:";
    private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec";
    private static String partStr="coverage";
    
    public static List<File> getDeletePath(Context context) {
        List<File> files = new ArrayList<>();
        File sdDir = new File(context.getFilesDir().getPath());
        String[] list = sdDir.list();
        if (list != null) {
            for(int i=0;i<list.length;i++){
                if(list[i].contains(partStr)){
                    files.add(new File(sdDir.getPath() + "/" + list[i]));
                }
            }
        }
        return files;
    }

    /**
     * 删除覆率数据文件
     * @param context
     */
    public static void deleteCoverageFiles(Context context){
        List<File> files = getDeletePath(context);
        if (files!= null && files.size() > 0) {
            for(File file:files){
                Log.d(TAG, "JacocoUtils_generateEcFile: 清除旧的ec文件path:+"+ file.getPath());
                // FileUtils.deleteFile(file);
                boolean result = file.delete();
                if (!result && file.exists()) {
                    try{
                        throw new IOException("Failed to delete " + file.getAbsolutePath());
                    }catch(IOException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void onJacocoCreate(Context context) {
        Log.d(TAG, "onJacocoCreate");
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss");
        Calendar cal = Calendar.getInstance();
        String create_time = format.format(cal.getTime()).substring(0,19);
        // 获取packagemanager的实例
        PackageManager packageManager = context.getPackageManager();
        // getPackageName()是你当前类的包名,0代表是获取版本信息
        try{
            //删除原来的覆盖率数据文件
            deleteCoverageFiles(context);
            //生成新覆盖率数据文件名
            PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(),0);
            String app_version = packInfo.versionName;
            DEFAULT_COVERAGE_FILE_PATH = context.getFilesDir().getPath() + "/coverage"+"-"+app_version+"-"+create_time+".ec";
        }catch(PackageManager.NameNotFoundException e){
            e.printStackTrace();
            Log.d(TAG,"找不到包名"+e);
        }
    }

    /**
     * 生成覆盖率数据文件
     * @param context
     */
    public static void generateCoverageFile(Context context) {
        OutputStream out = null;
        try {
            //如果文件不存在,创建覆盖率数据文件
            File file = new File(DEFAULT_COVERAGE_FILE_PATH);
            if(!file.exists()){
                try{
                    file.createNewFile();
                }catch (IOException e){
                    Log.d(TAG,"新建文件异常:"+e);
                    e.printStackTrace();}
            }
            //将内存中的覆盖率数据写入到文件中
            out = new FileOutputStream(DEFAULT_COVERAGE_FILE_PATH, true);
            Object agent = Class.forName("org.jacoco.agent.rt.RT")
                    .getMethod("getAgent", new Class[0]).invoke(null);

            out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
                    .invoke(agent, false));

            Log.d(TAG, "生成覆盖率数据文件:"+DEFAULT_COVERAGE_FILE_PATH);
        } catch (Exception e) {
            Log.d(TAG, e.toString(), e);
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

说明:

  • 本代码借助于jacoco.agent将手机内存中的覆盖率数据文件写入到文件中;
  • 默认文件路径是本应用的files文件夹,由于现在高版本的android系统不允许访问手机存储,只能存储到App的本身空间中,所以在AndroidManifest.xml文件中要添加:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • 代码中有初始化,清除原来覆盖率文件的函数,也有生成覆盖率文件的函数,只要在适合的时机调用即可。
  • 文件命名:coverage-app版本号-日期-时间.ec 如:coverage-1.0-2025-02-11-10_50_03.ec。

2.2.3 何时生成覆盖率文件?

通过专门的类,可以将手机中的覆盖率数据写入到应用的空间中,保存成覆盖率文件。现在存在一个问题,什么时候保存覆盖率数据文件?由于覆盖率数据存在于内存中,一旦应用退出 ,数据将被清除。分析一下app的生命周期,不难发现:
  • 在app进入前端时,清除原来的覆盖率数据文件,开始采集覆盖率数据;
  • 在app进入后台时,生成覆盖率数据文件。
这样交互进行比较合适。如果你的应用中有生命周期控制类,在相应的函数中引用上面的覆盖率生成函数即可,如果没有,请按如下方法,在MainActivity中的onCreate函数中添加生命周期控制函数,如下所示:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
   .....
    // 注册LifecycleObserver
    ProcessLifecycleOwner.get().getLifecycle().addObserver(new LifecycleObserver() {
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        public void onMoveToForeground() {
            //清除原来的覆盖率数据文件
            GenerateECFile.onJacocoCreate(MainActivity.this);
            // 应用从后台移动到前台时调用
            Log.i(TAG,"App moved to foreground");
        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        public void onMoveToBackground() {
            //开始生成覆盖率数据文件
            GenerateECFile.generateCoverageFile(MainActivity.this);
            // 应用从前台移动到后台时调用
            Log.i(TAG,"App moved to background");
        }
    });
    ....
    }

 添加了以上操作,就可以在App的生命周期中采集覆盖率数据,并写入到文件中。

       将通过上面修改的app打包,安装到手机上进行测试。打开计算器,随便进行一些操作或是执行一些测试用例,然后再将app置于后台,注意不要杀死App。此时会将前面操作的覆盖率数据写入数据文件,置到后台一会儿后,再杀死应用,就可以拿覆盖率数据文件了。

2.2.4 下载覆盖率数据文件

    根据设置覆盖率数据文件会生成在手机下面的位置:/data/data/应用包名/files,但是正常的手机系统由于安全设置,是无法下载下来的:
此时,在Android Studio点击右侧的Device Manager,找到连接的手机设备,单击设备最右侧的按钮,选择"Open in Device Explorer",就可以打开手机的文件系统,如下所示:
     找到对应的覆盖率数据文件的位置,如:/data/data/com.example.calculator/files/,就可以看到覆盖率数据文件,右击文件,选择"save as..".将覆盖率数据文件下载到本地目录。
     下载到覆盖率数据文件后,就可以根据需要生成全量和增量覆盖率报告,检测测试情况,排查漏测问题补充测试用例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2320050.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3中router最佳封装落地

文章目录 前言一、拆分路由文件夹&#xff1f;二、main.ts中注册路由总结 前言 router在使用过程中如果我们直接在一个文件的一个数组中配置&#xff0c;最后路由越来越多会导致不易管理&#xff0c;我们可以将一个页面的路由配置在一个数组中最后统一导入&#xff0c;这样就会…

网络爬虫【爬虫库request】

我叫不三不四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步 今天来讲一讲爬虫 Requests是Python的一个很实用的HTTP客户端库&#xff0c;完全满足如今网络爬虫的需求。与Urllib对比&#xff0c;Requests不仅具备Urllib的全部功能&#xff1b;在开发使用上&…

aws(学习笔记第三十四课) dockerized-app with asg-alb

aws(学习笔记第三十四课) dockerized-app with asg-alb 使用cdk生成dockerized-app并使用AutoScalingGroup和ApplicationLoaderBalancer 学习内容&#xff1a; 使用cdk生成dockerized-app并使用AutoScalingGroup和ApplicationLoaderBalancer在AutoScalingGroup中使用efs以及R…

嵌入式c学习七

c语言指针&#xff1a;程序需要载入内存中运行&#xff0c;在32bit系统中内存地址的范围是&#xff1a;0x0000 0000-0xFFFF FFFF&#xff0c;内存大小为4GB&#xff0c;内存地址指的是内存单元的编号是固定的&#xff0c;本身就是一个整数&#xff0c;对于32bit系统&#xff0c…

软考通关利器:中级软件设计师结构化开发核心考点

简介&#xff1a; 作为国家软考中级认证的核心科目&#xff0c;“软件设计师” 结构化开发能力是职业进阶的黄金敲门砖。本模块聚焦考试大纲高频考点&#xff0c;深度解析需求建模、结构化分析方法&#xff08;SA/SD&#xff09;、模块设计原则、数据流图&#xff08;DFD&#…

【Linux】Hadoop-3.4.1的伪分布式集群的初步配置

配置步骤 一、检查环境 JDK # 目前还是 JDK8 最适合 Hadoop java -version echo $JAVA_HOME Hadoop hadoop version echo $HADOOP_HOME 二、配置SSH免密登录 Hadoop需要通过SSH管理节点&#xff08;即使在伪分布式模式下&#xff09; sudo apt install openssh-server …

楼宇自控系统的结构密码:总线与分布式结构方式的差异与应用

在现代建筑中&#xff0c;为了实现高效、智能的管理&#xff0c;楼宇自控系统变得越来越重要。它就像建筑的 智能管家&#xff0c;可自动控制照明、空调、通风等各种机电设备&#xff0c;让建筑运行更顺畅&#xff0c;还能节省能源成本。而在楼宇自控系统里&#xff0c;有两种关…

Fourier-Lerobot——把斯坦福人形动作策略iDP3封装进了Lerobot(含我司七月人形研发落地实践)

前言 近期在抠lerobot源码时&#xff0c;看到其封装了ALOHA ACT、diffusion policy、π0时&#xff0c;我就在想&#xff0c;lerobot其实可以再封装下idp3 我甚至考虑是否从我联合带的那十几个具身研究生中选几个同学做下这事&#xff0c;对他们也是很好的历练然当25年3.18日…

系统架构设计知识体系总结

1.技术选型 1.什么是技术选型&#xff1f; 技术选型是指评估和选择在项目或系统开发中使用的最合适的技术和工具的过程。这涉及考虑基于其能力、特性、与项目需求的兼容性、可扩展性、性能、维护和其他因素的各种可用选项。技术选型的目标是确定与项目目标相符合、能够有效解…

计划管理工具应该具备的能(甘特图)

在当今快节奏的项目管理环境中&#xff0c;高效地规划和跟踪项目进度是至关重要的。甘特图&#xff0c;作为项目管理领域的经典工具&#xff0c;以其直观的时间轴和任务分配方式&#xff0c;深受项目管理者的青睐。 随着数字化时代的到来&#xff0c;甘特图线上编辑器应运而生&…

简单实用!百度AI + Raphael AI = 免费生图

简单实用&#xff01;百度AI Raphael AI 免费生图 -- ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b55eda9141d34697b05db0cd60f62b75.png#pic_center) 第一步&#xff1a;下载或截取一些好看的图片当参考图片 第二步&#xff1a;用百度AI描述你想要的图片&…

2025-03-22 学习记录--C/C++-C 库函数 - getchar()

C 库函数 - getchar() ⭐️ C 标准库 - <stdio.h> &#xff08;一&#xff09;、函数声明 &#x1f36d; int getchar(void)从标准输入 stdin 获取一个字符&#xff08;一个无符号字符&#xff09;。 参数&#xff1a;&#x1f380; NA 返回值&#xff1a;&#x1f38…

APM 仿真遥控指南

地面站开发了一段时间了&#xff0c;由于没有硬件&#xff0c;所以一直在 APM 模拟器中验证。我们已经实现了 MAVLink 消息接收和解析&#xff0c;显示无人机状态&#xff0c;给无人机发送消息&#xff0c;实现一键起飞&#xff0c;飞往指定地点&#xff0c;降落&#xff0c;返…

BBR 和 CUBIC 对长肥管道的不同反应

有个关于 CUBIC(等一众 AIMD-based cc) 和 BBR 在长肥管道中的行为比较挺有趣&#xff0c;它们的表现竟然截然相反&#xff1a; CUBIC 流共存&#xff0c;RTT 越大&#xff0c;Goodput 越低&#xff1b;BBR 流共存&#xff0c;RTT 越大&#xff0c;Goodput 越高。 前一个被看…

架构师面试(十九):IM 架构

问题 IM 系统从架构模式上包括 【介绍人模式】和 【代理人模式】。介绍人模式也叫直连模式&#xff0c;消息收发不需要服务端的参与&#xff0c;即客户端之间直连的方式&#xff1b;代理人模式也叫中转模式&#xff0c;消息收发需要服务端进行中转。 下面关于这两类模式描述的…

Spring框架入门指南:从Hello World到IOC容器

第一章&#xff1a;Spring框架的介绍 1. Spring框架的概述 Spring是一个开放源代码的设计层面框架&#xff0c;它解决的是业务逻辑层和其他各层的松耦合问题&#xff0c;因此它将面向接口的编程思想贯穿整个系统应用。 Spring是于2003 年兴起的一个轻量级的Java开发框架&…

嵌入式电路设计软件个人安装步骤分享

各位小伙伴大家好,今天给大家分享一个,电路设计软件的安装方法,希望对大家有所帮助。 一、下载【Multisim14.0安装包】: 链接:夸克网盘分享 提取码:kHSP 电脑安装Multisim14.0并且汉化 准备安装包以及汉化包双击“NI_Circuit_Design_Suite_14_0【海量免费资源:kebaiwan…

git | 回退版本 并保存当前修改到stash,在进行整合。[git checkout | git stash 等方法 ]

目录 一些常见命令&#xff1a; git 回退版本 一、临时回退&#xff08;不会修改历史&#xff0c;可随时回到当前版本&#xff09; 方法1&#xff1a;git checkout HEAD~1 二、永久回退&#xff08;改变分支指向&#xff09; 方法2&#xff1a;git reset 1. 保留修改&am…

【Java SE】单例设计模式

参考笔记&#xff1a;深入理解Java设计模式&#xff1a;单例模式及其饿汉式与懒汉式的对比,-CSDN博客 目录 1.什么是设计模式 2.经典设计模式 3.单例设计模式&#xff08;static属性/方法经典使用场景 &#xff09; 3.1 饿汉式单例模式 3.2 懒汉式单例模式 4.补充 1.什么…

安全守护:反光衣检测技术的革新之路

视觉分析助力船上工人反光衣检测 在现代工业生产与作业环境中&#xff0c;安全始终是首要考虑的因素。对于水上作业&#xff0c;如船舶维护、海上施工等场景&#xff0c;工人穿戴反光衣是预防事故、提高可见性的重要措施。然而&#xff0c;传统的人工检查方式不仅效率低下&…