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

        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 falsetestCoverageEnabled = trueproguardFiles 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);.....// 注册LifecycleObserverProcessLifecycleOwner.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.mzph.cn/pingmian/73251.shtml

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

相关文章

特征工程自动化(FeatureTools实战)

目录 特征工程自动化(FeatureTools实战)1. 引言2. 项目背景与意义2.1 特征工程的重要性2.2 自动化特征工程的优势2.3 工业级数据处理需求3. 数据集生成与介绍3.1 数据集构成3.2 数据生成方法4. 自动化特征工程理论基础4.1 特征工程的基本概念4.2 FeatureTools库简介4.3 关键公…

Scikit-learn模型评估全流程解析:从数据划分到交叉验证优化

模型评估的步骤、scikit-learn函数及实例说明 1. 数据划分&#xff08;Train-Test Split&#xff09; 函数&#xff1a;train_test_split使用场景&#xff1a;将数据分为训练集和测试集&#xff0c;避免模型过拟合。作用&#xff1a;确保模型在未见过的数据上验证性能。示例&…

Spring AI相关的面试题

以下是150道Spring AI相关的面试题目及答案&#xff1a; ### Spring AI基础概念类 **1. 什么是Spring AI&#xff1f;** Spring AI是Spring框架的扩展&#xff0c;旨在简化人工智能模型在Java应用中的集成与使用&#xff0c;提供与Spring生态无缝衔接的工具和抽象&#xff0c…

C++ 学习笔记(四)—— 类和对象

1、this指针 class Date { public&#xff1a;void Init(Date* this, int year, int month, int day){this->_year year;this->_month month;this->_day day;this->Print();// 这就是this指针&#xff0c;是编译器自己加的&#xff0c;是用来让成员函数找到成…

SpringMVC全局异常处理机制

异常处理机制 异常处理的两种方式&#xff1a; 编程式异常处理&#xff1a;是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理&#xff0c;例如使用 try-catch 块来捕获异常&#xff0c;然后在 catch 块中编写特定的处理代码&#xff0c;或者在 f…

深入LangChain:LLM交互机制与RAG集成的技术

本文将聚焦于 LangChain 如何集成检索增强生成&#xff08;RAG&#xff09;&#xff0c;了解其架构、主要组件&#xff0c;以及与 LLM 的交互 LangChain 架构概览 1、基础层 这是与各类 LLM 对接的 “桥梁”。LangChain 支持多种流行的 LLM&#xff0c;如 OpenAI 的系列模型、H…

本地部署 LangManus

本地部署 LangManus 0. 引言1. 部署 LangManus2. 部署 LangManus Web UI 0. 引言 LangManus 是一个社区驱动的 AI 自动化框架&#xff0c;它建立在开源社区的卓越工作基础之上。我们的目标是将语言模型与专业工具&#xff08;如网络搜索、爬虫和 Python 代码执行&#xff09;相…

SQL注入(SQL Injection)攻击原理与防御措施

SQL是一种代码注入技术&#xff0c;可使攻击者修改应用程序向数据库提供的查询。 迄今为止&#xff0c;最常见和最严重的应用 程序安全威胁总是隐藏在与数据库有某些连接的网络应用 程序中。 通过这种 SQL 注入&#xff0c;攻击者可以绕过登录程序&#xff0c;获取、更改甚至更…

【算法】十大排序算法(含时间复杂度、核心思想)

以下是 **十大经典排序算法** 的时间复杂度、空间复杂度及稳定性总结&#xff0c;适用于面试快速回顾&#xff1a;排序算法对比表 排序算法最佳时间复杂度平均时间复杂度最差时间复杂度空间复杂度稳定性核心思想冒泡排序O(n)O(n)O(n)O(1)稳定相邻元素交换&#xff0c;大数沉底…

LVS的 NAT 模式实现 3 台RS的轮询访问

使用LVS的 NAT 模式实现 3 台RS的轮询访问 1.配置 RS&#xff08;NAT模式&#xff09;2. 配置 LVS 主机&#xff08;仅主机、NAT模式&#xff09;2.1 配置仅主机网卡&#xff08;192.168.66.150/24 VIP &#xff09;2.2 配置 NAT 网卡&#xff08;192.168.88.6/24 DIP&#xff…

一、MySQL8的my.ini文件

MySQL8.0.11的安装版本my.ini配置文件默认存放在&#xff1a;C:/Program Files/MySQL/MySQL Server 8.0/ 目录下&#xff1b;而MySQL8.0.11绿色免安装版本是没有my.ini配置文件&#xff0c;用户可以自行构建后&#xff0c;再通过my.ini进行数据库的相关配置 一、MySQL8.0.11默…

微调这件小事:训练集中的输入数据该作为instruction还是input?从LLaMA-Factory的源码中寻找答案吧~

在之前的博文中,我们已经了解了LLaMA-Factory框架执行各类任务的流程。今天,我们将深入探讨SFT微调过程中关于数据集的两个关键问题: 数据集中的instruction和input是如何结合起来生成大模型可以理解的输入的?instruction是不是就是system prompt呢?(之所以会问这个问题,…

nacos-actuator漏洞

1、nacos配置文件添加以下配置 vim application.properties# 添加以下配置项 management.endpoints.enabled-by-defaultfalse management.server.port-12、重启Nacos systemctl restart nacos3、验证 打开地址http://ip:port/nacos/actuator查看是否有敏感信息输出&#xff0…

extern关键字的用法

目录 总述 一、声明外部变量 二、声明外部函数 三、实现模块化编程 四、与"C" 连用&#xff0c;实现C和C的混合编程 五、注意事项 六、疑点补充&#xff08;你可能会有和我一样的疑问&#xff1f;&#xff09; 总述 在C和C中&#xff0c;extern关键字用于声明外…

Jboss漏洞再现

一、CVE-2015-7501 1、开环境 2、访问地址 / invoker/JMXInvokerServlet 出现了让下载的页面&#xff0c;说明有漏洞 3、下载ysoserial工具进行漏洞利用 4、在cmd运行 看到可以成功运行&#xff0c;接下来去base64编码我们反弹shell的命令 5、执行命令 java -jar ysoserial-…

Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现

一、前言 在移动互联网蓬勃发展的今天&#xff0c;视频播放功能已成为众多Android应用的核心特性之一。面对多样化的视频格式和传输协议&#xff0c;开发一款高效、稳定的视频播放器是许多开发者追求的目标。FLV&#xff08;Flash Video&#xff09;格式&#xff0c;尽管随着H…

BUAA XCPC 2025 Spring Training 2

C \color{green}{\texttt{C}} C [Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription] 给定一棵以 1 1 1 为根的树&#xff0c;记 a i a_{i} ai​ 表示节点 i i i 的权值&#xff0c; lca( i , j ) \text{lca(}i,j) lca(i,j) 表示节…

MySQL 中,分库分表机制和分表分库策略

在 MySQL 中,分库分表是一种常见的数据库水平扩展方案,用于解决单库单表数据量过大导致的性能瓶颈问题。通过将数据分散到多个数据库或表中,可以提高系统的并发处理能力、降低单点故障风险,并提升查询性能。 一、分库分表的作用 提升性能: 分散数据存储和查询压力,避免单…

组件日志——etcd

目录 一、简介 二、安装【Ubuntu】 安装etcd 安装CAPI 三、写一个示例 3.0写一个示例代码 3.1获取一个etcd服务 3.2获取租约(写端操作) 3.3使用租约(写端操作) 3.4销毁租约(写端操作) 3.5获取etcd服务中的服务列表(读端操作) 3.6监听状态变化(读端操作) 一、简介 Et…

python网络爬虫开发实战之网页数据的解析提取

目录 1 XPath的使用 1.1 XPath概览 1.2 XPath常用规则 1.3 准备工作 1.4 实例引入 1.5 所有节点 1.6 节点 1.7 父节点 1.8 属性匹配 1.9 文本获取 1.10 属性获取 1.11 属性多值匹配 1.12 多属性匹配 1.13 按序选择 1.14 节点轴选择 2 Beautiful Soup 2.1 简介…