news 2026/4/22 17:43:51

用Jacoco检查你的手动测试覆盖率

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Jacoco检查你的手动测试覆盖率

团队中目前还没有自动化测试的覆盖,所以测试 team 想了解下手动测试的覆盖率。于是才有了本片文章的产生。网上有很多文章是利用 Android 的 instrument 测试框架,然后通过命令来启动app来进行测试。而且报告生产的时间点是在启动的 activity 结束以后,在复杂场景下,是没有办法来捕捉到所有页面的函数调用的。

本文中的方案是对一个新的build type来重载 Application 代码,只在手动测试时候使用,对原来的代码不会产生任何影响。希望可以帮到你。

1、在你的工程目录的 buildscripts 下,新建一个jacoco.gradle 的文件,添加如下代码

2、在 app 目录下的 build.gradle 中添加代码,来启用脚本

3、执行 testDebugUnitTest 后,会在app/build/jococo/ 下看到 testDebugUnitTest.exec。记住这个文件 我们会在后面用这个文件来生产报告。

4、创建jacoco 任务

Android gradle plugin 会生成不同的 variant, 所以我们要对不用的variant生成不用的任务来生产报告。

  1. project.afterEvaluate {

  2. android.applicationVariants.all { variant ->

  3. defvariantName= variant.name deftestTaskName="test${variantName.capitalize()}UnitTest"

  4. tasks.create(name: "${testTaskName}Coverage", type: JacocoReport, dependsOn: "$testTaskName") {

  5. //TODO 后面实现 }

  6. }}

点击Sync Gradle后,gradle task 会增加两个任务testDebugUnitTestCoverage,testReleaseUnitTestCoverage,接下来我们增加实现报告生成的任务。

5、使用一下代码替换上一个步骤中的TODO

  1. group ="Reporting"

  2. description ="Generate Jacoco coverage reports for the ${variantName.capitalize()} build."

  3. // 设置报告格式

  4. reports {

  5. html.enabled =true

  6. xml.enabled =true

  7. }

  8. // 排除不需要统计的类

  9. def excludes = [

  10. '**/R.class',

  11. '**/R$*.class',

  12. '**/BuildConfig.*',

  13. '**/Manifest*.*',

  14. '**/*Test*.*',

  15. 'android/**/*.*',

  16. 'androidx/**/*.*'

  17. ]

  18. // Java 类文件

  19. def javaClasses =fileTree(dir: variant.javaCompiler.destinationDir, excludes: excludes)

  20. // Kotlin 文件

  21. def kotlinClasses =fileTree(dir: "${buildDir}/tmp/kotlin-classes/${variantName}", excludes: excludes)

  22. classDirectories =files([javaClasses, kotlinClasses])

  23. // 源文件

  24. sourceDirectories =files([

  25. "$project.projectDir/src/main/java",

  26. "$project.projectDir/src/${variantName}/java",

  27. "$project.projectDir/src/main/kotlin",

  28. "$project.projectDir/src/${variantName}/kotlin"

  29. ])

  30. // 最开始我们生成的文件

  31. executionData =files("${project.buildDir}/jacoco/${testTaskName}.exec")

6、执行一下 testDebugUnitTestCoverage 任务,我们就会在build 目录下看到报告了

经过以上步骤我们完成了一个jacoco 报告的生成过程。

关键步骤来了,如何在打包的app中开启jacoco呢?

1、新建一个staging的build type

2、在src目录下,与 main 通级,新建 staging 目录

3、staging 目录下新建 java目录,并在 com.example.staging 包下新建 StagingApp.kt文件,代码如下:

  1. package com.example.stagingimport android.Manifestimport android.app.Activityimport android.app.Applicationimport android.os.Bundleimport android.os.Environmentimport android.util.Logimport android.widget.Toastimport androidx.fragment.app.FragmentActivityimport com.tbruyelle.rxpermissions2.RxPermissionsimport java.io.Fileimport java.io.FileOutputStreamimport java.io.IOExceptionclass StagingApp :Application() {

  2. overridefunonCreate() {

  3. super.onCreate()

  4. Log.d(TAG, "StagingApp")

  5. registerActivityLifecycleCallbacks(object: ActivityLifecycleCallbacks {

  6. var activitySize =0

  7. overridefunonActivityPaused(activity: Activity?) {

  8. }

  9. overridefunonActivityResumed(activity: Activity?) {

  10. // 第一个activity 请求 SD card 目录访问权限

  11. if (activitySize ==1) {

  12. (activity as? FragmentActivity)?.let {

  13. val rxPerm =RxPermissions(it)

  14. rxPerm.request(Manifest.permission.WRITE_EXTERNAL_STORAGE).subscribe({ result ->

  15. if (!result) {

  16. Toast.makeText(

  17. it,

  18. "You have to grant the permission to save coverage file",

  19. Toast.LENGTH_SHORT ).show()

  20. }

  21. }, { e ->

  22. e.printStackTrace()

  23. })

  24. }

  25. }

  26. }

  27. overridefunonActivityStarted(activity: Activity?) {

  28. }

  29. overridefunonActivityDestroyed(activity: Activity?) {

  30. activitySize -=1

  31. if (activitySize <=0) {

  32. //所有activity被销毁后,生产报告文件

  33. generateCoverageReport(createFile())

  34. }

  35. }

  36. overridefunonActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {

  37. }

  38. overridefunonActivityStopped(activity: Activity?) {

  39. }

  40. overridefunonActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {

  41. activitySize +=1

  42. }

  43. })

  44. }

  45. privatefungenerateCoverageReport(file: File) {

  46. Log.d(TAG, "generateCoverageReport():${file.absolutePath}")

  47. FileOutputStream(file, false).use {

  48. val agent = Class.forName("org.jacoco.agent.rt.RT")

  49. .getMethod("getAgent")

  50. .invoke(null)

  51. Log.d(TAG, agent.toString())

  52. it.write(

  53. agent.javaClass.getMethod("getExecutionData", Boolean::class.javaPrimitiveType)

  54. .invoke(agent, false) as ByteArray )

  55. }

  56. }

  57. funcreateFile(): File {

  58. // SD card 下面

  59. val file =File(Environment.getExternalStorageDirectory(), "jacoco/$DEFAULT_COVERAGE_FILE_PATH")

  60. if (!file.exists()) {

  61. try {

  62. file.parentFile?.mkdirs()

  63. file.createNewFile()

  64. } catch (e: IOException) {

  65. Log.d(TAG, "异常 : $e")

  66. e.printStackTrace()

  67. }

  68. }

  69. return file }

  70. companionobject {

  71. constval DEFAULT_COVERAGE_FILE_PATH ="jacoco-coverage.ec"

  72. constval TAG ="StagingApp"

  73. }}

4、staging 目录中新建一个 AndroidManifest.xml 文件,内容如下

  1. <?xml version="1.0" encoding="utf-8"?><manifest

  2. xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.jacocomanual">

  3. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

  4. <application android:name="com.example.staging.StagingApp"/></manifest>

5、IDE Build Variants 下选择 staging

6、运行app并安装到设备或者模拟器,操作一下,然后按返回键关闭所有的页面,这时候会在 SD 卡目录下的生成 jacoco/jacoco-coverage.ec 文件。

7、复制 jacoco-coverage.ec 文件到项目根目录下的 jacoco 文件夹

8、我们来修改jacoco的任务来生成最后的报告

9、运行 testStagingUnitTest 这样就可以看到报告了

感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取

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

2026年AIGC落地关键:麦橘超然弹性GPU部署方案

2026年AIGC落地关键&#xff1a;麦橘超然弹性GPU部署方案 1. 麦橘超然 - Flux 离线图像生成控制台 在AI生成内容&#xff08;AIGC&#xff09;加速向产业渗透的2026年&#xff0c;如何让高性能图像生成模型真正“用得上、跑得起、控得住”&#xff0c;成为企业与开发者关注的…

作者头像 李华
网站建设 2026/4/23 11:19:38

AI图像修复技术趋势分析:GPEN开源项目如何高效落地生产环境

AI图像修复技术趋势分析&#xff1a;GPEN开源项目如何高效落地生产环境 1. 引言&#xff1a;从老照片到高清人像&#xff0c;AI修复正在改变视觉内容生态 你有没有翻过家里的老相册&#xff1f;泛黄的照片、模糊的轮廓、斑驳的痕迹——这些时间留下的印记&#xff0c;曾经只能…

作者头像 李华
网站建设 2026/4/23 9:56:31

API频繁超时?,一文掌握Dify节点重试配置最佳实践

第一章&#xff1a;API超时问题的根源与影响 API超时是分布式系统中常见但影响深远的问题&#xff0c;通常发生在客户端等待服务器响应超过预设时间阈值时。此类问题不仅影响用户体验&#xff0c;还可能导致服务级联失败&#xff0c;严重时引发系统雪崩。 常见超时原因 网络延…

作者头像 李华
网站建设 2026/4/22 10:11:33

海南海鲜热门榜单:琼海海鲜、琼海干锅鱼籽鱼泡、琼海香锅臭鲈鱼等五款美味推荐

在海南美食的海洋中&#xff0c;琼海海鲜以其独特魅力备受欢迎。无论是让人垂涎的琼海干锅鱼籽鱼泡&#xff0c;还是经典的海南地方菜中不可或缺的琼海香锅臭鲈鱼&#xff0c;这些美味都为食客们带来了无与伦比的味蕾享受。而琼海海鲜现做、新鲜活海鲜则确保了每一口都充满原汁…

作者头像 李华
网站建设 2026/4/23 9:52:08

【Dify高级运维技巧】:掌握DSL文件迁移,实现跨环境无缝部署

第一章&#xff1a;DSL文件迁移的核心价值与场景解析 在现代软件架构演进过程中&#xff0c;DSL&#xff08;领域特定语言&#xff09;文件的迁移已成为系统重构、平台升级和多环境适配的关键环节。DSL文件通常用于定义业务规则、配置流程逻辑或描述数据结构&#xff0c;其迁移…

作者头像 李华
网站建设 2026/4/23 11:11:46

【dify实战避坑手册】:为何段落长度是索引失败的头号元凶?

第一章&#xff1a;段落过长为何成为Dify知识库索引失败的罪魁祸首 在构建基于Dify的知识库系统时&#xff0c;内容分段质量直接影响向量化索引的准确性和检索效率。当输入文档包含过长的段落时&#xff0c;模型难以精准提取关键语义&#xff0c;导致嵌入向量表征模糊&#xff…

作者头像 李华