安卓app功能或自动化测试覆盖率统计(不用instrumentation启动app)
一文带你揭秘如何采取非instrumentation启动app,打造实时统计覆盖率,一键触发覆盖率测试报告。
在上篇文章,一文带你解决Android app手工测试或者自动化测试覆盖率统计(撸代码版),我们采用了instrumentation的方式去启动app,很多人会问,如果我们不用instrumentation启动app的方式,正常启动app进行测试,然后收集覆盖率可以吗,答案,是可以的,如何做呢,下面带你去揭晓其中的奥秘。
首先呢,我们还是基于我们的工作,去申请我们的读写的权限。
1 2 | <uses-permission android:name= "android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name= "android.permission.WRITE_EXTERNAL_STORAGE" /> |
申请后,我们在安装app的时候一定要给予这两个权限,接下来呢,我们去配置jacoco相关的。
在项目的build.gradle配置相关的,如下配置
1 2 3 4 5 6 | apply plugin: 'jacoco' jacoco { toolVersion = "0.8.4" description( "$buildDir/coverage.exec" ) reportsDir = file( "$buildDir/reports/jacoco" ) } |
首先我们去添加jacoco的插件,接着呢,我们去规定版本,然后去规定我们的覆盖文件的位置,接下来我们去告知下测试报告的位置。这样我们配置好了依赖,我们需要在debug打开覆盖率。还是同一个的build.gradle配置
1 | debug { <br> /**打开覆盖率统计开关*/ <br>testCoverageEnabled = true <br>minifyEnabled false //获取代码覆盖率需要设为false <br>proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } |
配置完毕后呢,我们去编写一个jacoco的工具类,用来处理覆盖率文件的写入。具体代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com.example.studayapp.test; import android.content.Context; import android.os.Environment; import android.util.Log; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class JacocoUtils { static String TAG = "JacocoUtils" ; private static String DEFAULT_COVERAGE_FILE_PATH = "coverage.exec" ; /** * 生成ec文件 * * @param isnew 是否重新创建exec文件 */ public static void generateEcFile( boolean isnew) { Log.d(TAG, "生成覆盖率文件: " + DEFAULT_COVERAGE_FILE_PATH); OutputStream out = null ; File mCoverageFilePath = new File(Environment.getExternalStorageDirectory(),DEFAULT_COVERAGE_FILE_PATH); try { if (isnew && mCoverageFilePath.exists()) { Log.d(TAG, "清除旧的exec文件" ); mCoverageFilePath.delete(); } if (!mCoverageFilePath.exists()) { mCoverageFilePath.createNewFile(); } out = new FileOutputStream(mCoverageFilePath.getPath(), true ); //反射:获取org.jacoco.agent.rt.IAgent Object agent = Class.forName( "org.jacoco.agent.rt.RT" ) .getMethod( "getAgent" ) .invoke( null ); //反射:getExecutionData(boolean reset),获取当前执行数据, // 以jacoco二进制格式转储当前执行数据 // getExecutionData(boolean reset),reset如果为true,则之后清除当前执行数据 out.write(( byte []) agent.getClass().getMethod( "getExecutionData" , boolean . class ) .invoke(agent, false )); } catch (Exception e) { Log.e(TAG, "generateEcFile: " + e.getMessage()); } finally { if (out == null ) return ; try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } } |
我们通过反射:获取org.jacoco.agent.rt.IAgent,然后,反射:getExecutionData(boolean reset),获取当前执行数据,最后写入执行的数据。
这样我们收集的数据的脚本下好呢,那么我们应该怎么去收集我们的数据呢,之前的文章是通过系统的返回键后去生成的,这样呢,其实在我们实际的工作中呢,是不常见呢,很多的时候呢,我们需要在特定的时候才去触发呢,这里呢,我的做法呢,是在设置中,增加一个按钮,生成测试覆盖率的 按钮来统一处理。
<Button
android:id="@+id/statistics"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="统计覆盖率">
</Button>
在布局文件呢,我们去创建一个按钮,然后呢,我们去在这个按钮去监听点击事件。
1 2 3 4 5 6 7 | statistics=(Button) findViewById(R.id.statistics); statistics.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { JacocoUtils.generateEcFile( true ); } }); |
这样呢,我们去安装我们的app的debug版本
然后呢,我们去正常测试,最后呢,我们去点击我们的按钮。生成完毕后,如下。
我们去在项目的目录下,我们去pull下来即可。
1 | adb pull /sdcard/coverage.exec . |
然后,我们在build.gradle创建一个任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | def coverageSourceDirs = [ '../app/src/main/java' ] task jacocoTestReport(type: JacocoReport) { group = "Reporting" description = "Generate Jacoco coverage reports after running tests." reports { xml.enabled = true html.enabled = true } classDirectories = fileTree( dir: './build/intermediates/javac/debug/classes' , excludes: [ '**/R*.class' , '**/*$InjectAdapter.class' , '**/*$ModuleAdapter.class' , '**/*$ViewInjector*.class' ]) sourceDirectories = files(coverageSourceDirs) executionData = files( "$buildDir/coverage.exec" ) doFirst { new File( "$buildDir/intermediates/javac/debug/classes/" ).eachFileRecurse { file -> if (file.name.contains( '$$' )) { file.renameTo(file.path.replace( '$$' , '$' )) } } } } |
然后点击
执行完毕后。
我们看下实际的效果
可以看到有测试报告,我们打开看下。
这是最后的覆盖率测试的统计数据。
这里的数据呢,只是统计到了全量代码的,还有增量代码覆盖率统计,多个覆盖率文件的不同的如何进行组合。后续的文章会持续分享。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!