jacoco测试代码覆盖率_使用Jacoco测量多模块Android项目中的单元测试覆盖率:第2部分

本文详细介绍了如何使用JaCoCo工具在包含多个模块的Android项目中进行单元测试覆盖率的测量,旨在提升项目的测试质量。
摘要由CSDN通过智能技术生成

jacoco测试代码覆盖率

In the first article, we discovered one of the two key Gradle commands which come with Jacoco plugin — jacocoTestReport. As you now know, it can be used to measure code coverage in your project by generating detailed reports in HTML, XML, or CVS formats. This time I’d like to share my experience with the second piece of this puzzle — jacocoTestCoverageVerification.

第一篇文章中,我们发现了Jacoco插件随附的两个重要Gradle命令之一-jacocoTestReport 如您所知,它可以通过生成HTML,XML或CVS格式的详细报告来衡量项目中的代码覆盖率。 这次,我想与这个难题的第二部分( jacocoTestCoverageVerification)分享我的经验

As per the documentation, this task does the following:

根据文档,此任务执行以下操作:

Task for verifying code coverage metrics. Fails the task if violations are detected based on specified rules.

验证代码覆盖率指标的任务。 如果根据指定规则检测到违规,则使任务失败。

So as you might have already guessed, this tool can be used to check whether some coverage threshold is reached or not. Basically, you specify the minimum coverage measured in % and run the task. If your unit tests “visit” the desired % of your codebase then this task will silently complete. Otherwise, it will fail. This can be really helpful for development teams since it allows you to have a kind of enforcement for writing unit tests and all you need to do for that is to setup Jacoco and then simply ask your CI/CD to execute jacocoTestCoverageVerification along with other tasks for your pipelines.

因此,您可能已经猜到了,该工具可以用于检查是否达到某个覆盖率阈值。 基本上,您可以指定以百分比为单位的最小覆盖率,然后运行任务。 如果您的单元测试“访问”了所需的代码库百分比,则此任务将以静默方式完成。 否则,它将失败。 这对开发团队真的很有帮助,因为它可以让您强制执行某种编写单元测试的工作,而您所需要做的就是设置Jacoco,然后简单地要求您的CI / CD执行jacocoTestCoverageVerification 以及管道的其他任务。

Let’s get started with the configuration. I’ll continue from where we stopped in the previous article. As with jacocoTestReport, it would be still useful to have two different setups for Java/Kotlin and Android modules. Let’s add two more functions to the jacoco.gradle file.

让我们开始进行配置。 我将从上一篇文章中停止的地方继续。 与jacocoTestReport一样,为Java / Kotlin和Android模块设置两个不同的设置仍然很有用。 让我们向jacoco.gradle文件添加另外两个函数。

def setupAndroidCoverageVerification(threshold) {
task jacocoTestCoverageVerification(
type: JacocoCoverageVerification,
dependsOn: [ 'testDebugUnitTest' ]
) {
violationRules {
rule {
limit {
minimum = threshold
}
}
}
final def coverageSourceDirs = [
"$projectDir/src/main/java",
"$projectDir/src/main/kotlin"
]
final def debugTree = fileTree(
dir: "$buildDir/tmp/kotlin-classes/debug",
excludes: jacocoFileFilter
)
sourceDirectories.from = files(coverageSourceDirs)
classDirectories.from = files([debugTree])
executionData.from = fileTree(
dir: project.buildDir,
includes: ['jacoco/testDebugUnitTest.exec']
)
}}def setupKotlinCoverageVerification(threshold) {
jacocoTestCoverageVerification {
dependsOn test
violationRules {
rule {
limit {
minimum = threshold
}
}
}
}
}

Each of these functions registers jacocoTestCoverageVerification Gradle task for a given module. This task is derived from the JacocoCoverageVerification type and depends on testDebugUnitTest which means that launching the verification task will also execute your unit tests before doing the validation (which is quite expectable: we need to run all the tests before we can measure their coverage). These functions also have the threshold parameter so we can dynamically provide it from the calling side.

每个功能都为给定模块注册jacocoTestCoverageVerification Gradle任务。 此任务派生自JacocoCoverageVerification类型,并取决于testDebugUnitTest ,这意味着启动验证任务还将在执行验证之前执行您的单元测试(这是可以预期的:我们需要先运行所有测试,然后才能测量其覆盖范围)。 这些函数还具有threshold参数,因此我们可以从调用方动态提供它。

It is time to remember that we’re talking about the multi-module Gradle setup so hardcoding a single coverage threshold may not be a very good idea: most likely many modules would want to customize this value on their own based on the module type, stage of development, etc. The simplest way to achieve this is to define constants with a fixed name in module-level build.gradle files and then read its value during Jacoco configuration:

现在应该记住,我们在谈论多模块Gradle设置,因此硬编码单个覆盖率阈值可能不是一个好主意:很可能许多模块都希望根据模块类型自行定制此值,最简单的方法是在模块级build.gradle文件中使用固定名称定义常量,然后在Jacoco配置期间读取其值:

Module A’s build.gradle:

模块A的build.gradle

...ext {
jacocoCoverageThreshold = 0.6 // 60%}

Module B’s build.gradle:

模块B的build.gradle

...ext {
jacocoCoverageThreshold = 0.8 // 80%}

And back to our jacoco.gradle:

回到我们的jacoco.gradle

afterEvaluate { project ->
def ignoreList = jacocoIgnoreList
def projectName = project.name
if (ignoreList.contains(projectName)) {
println "Jacoco: ignoring project ${projectName}"
return false
} def threshold = project.hasProperty('jacocoCoverageThreshold')
? project.jacocoCoverageThreshold
: project.jacocoCoverageThresholdDefault

if (isAndroidModule(project)) {
setupAndroidReporting()
setupAndroidCoverageVerification(threshold)
} else {
setupKotlinReporting()
setupKotlinCoverageVerification(threshold)
}}

Note that jacocoTestCoverageThresholdDefault value will be used if some modules don’t want to provide a custom threshold and are fine with the project-level defaults. Where this default value can be stored? Let’s power-up thejacoco-ignore-list.gradle from the previous article by converting into a more global jacoco-config.gradle:

请注意,如果某些模块不想提供自定义阈值,并且使用项目级别的默认值,则将使用jacocoTestCoverageThresholdDefault值。 该默认值可以存储在哪里? 让我们通过转换成更具全局性的jacoco-config.gradle上一篇文章中的jacoco-ignore-list.gradle

project.ext {    jacocoCoverageThresholdDefault = 0.60    
jacocoIgnoreList = [
"module-name-1",
"module-name-2"
] // Exclude file by names, packages or types. Such files will be ignored during test coverage
// calculation
jacocoFileFilter = [
'**/*App.*',
'**/*Application.*',
'**/*Activity.*',
'**/*Fragment.*',
'**/*View.*',
'**/*ViewGroup.*',
'**/*JsonAdapter.*',
'**/di/**',
'**/*Dagger.*'
]}

We’re done with the basic setup: all modules now support jacocoTestCoverageVerification task. As with jacocoTestReport, it can be launched for individual modules by executing the following command

基本设置已完成:现在所有模块都支持jacocoTestCoverageVerification任务。 与jacocoTestReport ,可以通过执行以下命令为单个模块启动它

./gradlew module-name:jacocoTestCoverageVerification

./gradlew模块名称:jacocoTestCoverageVerification

Or you can verify all the modules at once by typing

或者您可以通过键入一次验证所有模块

./gradlew jacocoTestCoverageVerification

./gradlew jacocoTestCoverageVerification

Now you can quickly find modules with no tests and blame their authors, right? Not yet, but we’re close! :) It appeared that Jacoco and both its tasks ignore modules which don’t have any tests added, i.e. jacocoTestCoverageVerification will not fail with “0% test coverage” error for such modules for some reason, it will only fail if this module contains at least 1 unit tests and this test doesn’t provide the desired code coverage. Let’s add a custom workaround for this limitation.

现在,您可以快速找到没有测试的模块并责怪其作者,对吗? 还没有,但是我们已经接近了! :)看来Jacoco及其两个任务都忽略了未添加任何测试的模块,即jacocoTestCoverageVerification不会由于某些原因而因“ 0%测试覆盖率”错误而失败,只有在该模块包含以下内容时,它才会失败至少1个单元测试,并且此测试未提供所需的代码覆盖率。 让我们为该限制添加自定义解决方法。

First, create a new task inside jacoco.gradle :

首先,在jacoco.gradle创建一个新任务:

class TestExistenceValidation extends DefaultTask {    static final SRC_DIR = 'src'
static final JAVA_DIR = 'java'
static final TEST_DIRS = ['test', 'androidTest'] @TaskAction
void execute() {
if (shouldSkip(project)) return // implement `shouldSkip` as required for your project or just remove this line File srcDir = new File(project.projectDir, SRC_DIR)
FileFilter filter = { it.isDirectory() }
File[] subDirs = srcDir.listFiles(filter) ?: []
File testsDir = subDirs.find { TEST_DIRS.contains(it.name) }
if (testsDir) {
File javaTestsDir = testsDir
.listFiles(filter)
.find { it.name == JAVA_DIR }
if (javaTestsDir && javaTestsDir.list().length > 0) {
return
}
} throw new GradleException("${project.name} has no unit tests.")
}
}

This task will simply make sure that each module contains non-empty test folders (either test or androdiTes). This logic can be further extended if needed, but I decided to don’t be too paranoid about that. Now we need to link this newly created testExistenceValidation to the verification task. As you already know, it can be achieved by usingdependsOn:

该任务将简单地确保每个模块都包含非空的测试文件夹( testandrodiTes )。 如果需要,可以进一步扩展此逻辑,但是我决定对此不要太偏执。 现在,我们需要将此新创建的testExistenceValidation链接到验证任务。 如您所知,可以通过使用dependsOn来实现:

def setupAndroidCoverageVerification(threshold) {...
dependsOn: [
'testExistenceValidation', // repeat for setupKotlinCoverageVerification
'testDebugUnitTest'
]
}

Done! Our verification task now also depends on testExistenceValidation routine and it will automatically execute it before doing its main job.

做完了! 现在,我们的验证任务还取决于testExistenceValidation例程,它将在执行主要任务之前自动执行它。

P.S.: complete versions of all source files I mentioned here can be found in this Gist.

PS:我在这里提到的所有源文件的完整版本可以在Gist中找到。

翻译自: https://medium.com/swlh/measuring-unit-test-coverage-in-multi-module-android-projects-using-jacoco-part-2-352ef949ecfb

jacoco测试代码覆盖率

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值