Android Jacoco覆盖率统计配置

Android Jacoco 覆盖率统计Gradle配置,包括生成本地单元测试报告,仪器单元测试报告,合并两种测试的报告,合并两种测试的执行数据并在AndroidStudio的编辑器中查看每一行的覆盖率情况。

如何让测试任务生成 Jacoco 覆盖率统计数据?

这里我们仅仅从Gradle任务来说,不考虑 AndroidStudio/IDEA。

本地单元测试(Test)

对于本地单元测试来说,原先有一个 testDebugUnitTest 的测试任务,如果不做配置,该任务只会生成测试通过情况的报告。只要应用 jacoco 插件,然后运行 testDebugUnitTest 任务时,就会同时生成jacoco覆盖率统计执行数据文件

// build.gradle 
apply(plugin = "jacoco")

之所以能这样是因为 jacoco 插件会给所有 Test 类型的任务添加 jacoco 的配置。

可以通过如下方式输出其执行数据文件路径:

afterEvaluate {
    tasks.getByName("testDebugUnitTest").apply {
        doLast {
            (this.extensions.getByName("jacoco") as JacocoTaskExtension).apply {
                logger.lifecycle("testDebugUnitTest dest: ${this.destinationFile?.absolutePath}")
            }
        }
    }
}

执行情况如下:

testDebugUnitTest dest: /build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec

仪器单元测试(AndroidTest)

仪器单元测试覆盖率数据的统计需要打开testCoverageEnabled开关,然后会有一个createDebugCoverageReport 的任务生成,同时也会生成html的报告。

// groovy
android {
    buildTypes {
        debug {
            testCoverageEnabled = true
        }
    }
}

// kotlin
android {
    buildTypes {
        getByName("debug").apply {
            isTestCoverageEnabled = true
        }
    }
}

连接设备执行该任务即可生成对应的执行数据文件及对应的覆盖率报告。

通过在build.gradle中添加如下配置可以在执行时输出其执行数据文件在本机的位置:

    tasks.withType(com.android.build.gradle.internal.coverage.JacocoReportTask::class.java) {
        logger.lifecycle("-> JacocoReportTask task : $name")
        doFirst {
            logger.lifecycle("-> JacocoReportTask task : $name , ${jacocoConnectedTestsCoverageDir.get()})}")
        }
    }

然后执行createDebugCoverageReport, 输出如下 :

> Task :app:createDebugAndroidTestCoverageReport
-> JacocoReportTask task : createDebugAndroidTestCoverageReport , /Users/hanlyjiang/Wksp/project/AndroidTestSample/app/build/outputs/code_coverage/debugAndroidTest/connected)}

> Task :app:createDebugCoverageReport

小结

通过以上信息我们可知:

  1. 应用 jacoco 插件之后,本地单元测试任务(testDebugUnitTest)就会生成 jacoco 覆盖率execution数据文件,但是不会生成html报告;
  2. 开启 isTestCoverageEnabled 开关,会生成 createDebugCoverageReport 任务,该任务会执行仪器单元测试,同时生成execution数据文件,并生成html报告。

生成 HTML 报告

由于androidTest 已经生成了html报告,接下来我们需要要为我们的本地单元测试生成HTML报告。

要生成html报告,我们需要一个类型为 JacocoReport 的任务,我们在gradle 中添加如下配置,用于生成 jacocoTestDebugUnitTestReport 任务

apply(plugin = "jacoco")

android {
    buildTypes {
        getByName("debug").apply {
            isTestCoverageEnabled = true
        }
    }
}
afterEvaluate {
    // 由于我们需要获取对应的源码及class目录,所以使用 android.applicationVariants forEach 来获取变体
    android.applicationVariants.forEach { variant ->
        if (variant.buildType.isTestCoverageEnabled) {
            val variantCapName = variant.name.capitalize();
            tasks.register(
                "jacocoTest${variantCapName}UnitTestReport",
                JacocoReport::class.java
            ) {
                group = "jacoco"
                // 依赖测试任务
                  dependsOn("test${variantCapName}UnitTest")
                
                // 根据执行数据生成报告,直接传输task即可
                executionData(tasks.getByName("test${variantCapName}UnitTest"))
                // 报告中会包含源码,可以查看源码的对应的覆盖情况
                sourceDirectories.from(variant.sourceSets.flatMap { it.javaDirectories + it.kotlinDirectories })
                // 没有 class 数据报告会是空的
                classDirectories.from(variant.javaCompileProvider.get().destinationDirectory)
              
                doLast {
                    logger.lifecycle(reports.html.outputLocation.asFile.get().absolutePath)
                }
            }
        }
    }
}

添加之后 sync gradle,即可生成一个 jacocoTestDebugUnitTestReport 的任务,执行它即可生成测试报告,生成的测试报告位于: build/reports/jacoco/jacocoTestDebugUnitTestReport中。

下图就是我们生成的报告,可以看到StringUtils已经能够统计覆盖率了。而MainActivity还没有数据。

图片

生成合并HTML报告

我们已经可以生成本地单元测试的覆盖率报告,现在我们需要生成androidTest + test 的合并报告。

之前我们已经知道:

  • createDebugCoverageReport 任务即可生成 execution 文件并且生成html报告
  • 我们已经添加了一个任务可以生成本地单元测试的报告。

现在我们要做的是将它们合并,但是我们的合并并不是针对html报告,而是针对execution数据。

让我们添加如下配置来生成一个合并报告的gradle任务:

apply(plugin = "jacoco")

android {
    buildTypes {
        getByName("debug").apply {
            isTestCoverageEnabled = true
        }
    }
}

afterEvaluate {
    android.applicationVariants.forEach { variant ->
        if (variant.buildType.isTestCoverageEnabled) {
            val variantCapName = variant.name.capitalize();
            tasks.register("mergedJacoco${variantCapName}TestReport", JacocoReport::class.java) {
                group = "jacoco"
                executionData(
                    tasks.getByName("test${variantCapName}UnitTest"),
                )
                val androidCoverageTask =
                    tasks.getByName("create${variantCapName}AndroidTestCoverageReport")
                if (androidCoverageTask is com.android.build.gradle.internal.coverage.JacocoReportTask) {
                    executionData(androidCoverageTask.jacocoConnectedTestsCoverageDir.asFileTree)
                }
                sourceDirectories.from(variant.sourceSets.flatMap { it.javaDirectories + it.kotlinDirectories })
                classDirectories.from(variant.javaCompileProvider.get().destinationDirectory)
                dependsOn(
                    "test${variantCapName}UnitTest",
                    "create${variantCapName}AndroidTestCoverageReport"
                )
                doLast {
                    logger.lifecycle(reports.html.outputLocation.asFile.get().absolutePath)
                }
            }
        }
    }
}

这样,我们便有了一个 mergedJacocoDebugTestReport 的任务,执行后即可在build/reports/jacoco/mergedJacocoDebugTestReport/html 目录中找到我们的 report 。

image

现在可以看到,我们的MainActivity(AndroidTest)及StringUtils(test)可以在一份报告中显示覆盖率数据了。

生成合并 Execution 数据文件

到现在为止,我们已经生成了HTML版本的合并报告,并且可以在其中看到源码没一行覆盖的情况。

图片

但是,我们希望能够在AndroidStudio的编辑器中显示覆盖率的情况,向下面这样:

图片

实际上,我们可以通过AndroidStudio的Menu-Run-Show Covarage Data加载 execution 文件,然后在AndroidStudio中显示覆盖率数据。

图片

执行的数据文件位于类似如下目录 :

  • build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
  • build/outputs/code_coverage/debugAndroidTest/connected/Pixel_5_API_Tiramisu(AVD) - 12/coverage.ec

但是这里有两个问题:

  1. debugAndroidTest 目录中的 .ec 文件无法被AndroidStudio识别。
  2. 这两个文件是分开的,每一个文件只包含一种测试的覆盖数据。

现在,让我们添加一个合并任务:


afterEvaluate {
   
    android.applicationVariants.forEach { variant ->
        if (variant.buildType.isTestCoverageEnabled) {
            val variantCapName = variant.name.capitalize();

            tasks.register("mergeJacoco${variantCapName}Execution", JacocoMerge::class.java) {
                group = "jacoco"
                executionData(
                    tasks.getByName("test${variantCapName}UnitTest"),
                )
                val androidCoverageTask =
                    tasks.getByName("create${variantCapName}AndroidTestCoverageReport")
                if (androidCoverageTask is com.android.build.gradle.internal.coverage.JacocoReportTask) {
                    executionData(androidCoverageTask.jacocoConnectedTestsCoverageDir.asFileTree)
                }
                doLast {
                    logger.lifecycle("Jacoco Execution: " + destinationFile.absolutePath)
                }
            }

        }
    }
}

执行之后位于:build/jacoco/mergeJacocoDebugExecution.exec, 通过AndroidStudio 加载之后,显示如下,两种测试的结果已经合并显示了。

图片

完整配置文件

请参考: AndroidTestSample/build.gradle.kts at main · hanlyjiang/AndroidTestSample (github.com)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容