3

What i want:

I want to match code coverage threshold to minimum value like 60% etc in Android gradle.

What i have tried

stack overflow question i have looked into

jacoco plugin gradle

Problem i am facing

My Gradle file is written like:

apply plugin: 'com.android.application'

apply plugin: 'jacoco'


jacoco {
    toolVersion = "0.7.6.201602180812"

    reportsDir = file("$buildDir/customJacocoReportDir")
}

def coverageSourceDirs = [
        'src/main/java',
]

jacocoTestReport {

reports {

        xml.enabled false
        csv.enabled false
        html.destination "${buildDir}/jacocoHtml"
    }
}



jacocoTestCoverageVerification {

    violationRules {
        rule {
            limit {
                minimum = 0.5
            }
        }

        rule {
            enabled = false
            element = 'CLASS'
            includes = ['org.gradle.*']

            limit {
                counter = 'LINE'
                value = 'TOTALCOUNT'
                maximum = 0.3
            }
        }
    }
}

Now, When i sync my gradle file i face following erros

Could not find method jacocoTestReport() for arguments [build_5t0t9b9hth6zfihsyl5q2obv8$_run_closure2@41a69b20] on project ':app' of type org.gradle.api.Project.

and if i comment jacocoTestReport task then

Could not find method jacocoTestCoverageVerification() for arguments [build_5t0t9b9hth6zfihsyl5q2obv8$_run_closure2@3da790a8] on project ':app' of type org.gradle.api.Project.

I am not able to understand what exactly going on here. Why jacocoTestCoverageVerification method not in plugin. What i am doing wrong.

Is it something gradle is picking jacoco plugin from android plugin?

I have tried mentioning version of jacoco to 0.6.3 as mentioned in there doc that jacocoTestCoverageVerification method is written above this version.

It'll be very helpful if anybody can sort out this problem.

Let me know in any other info required.

Thanks

Community
  • 1
  • 1
Ankur Chaudhary
  • 2,709
  • 3
  • 19
  • 30

2 Answers2

4

This problem keeps haunting me for a few times, every time the keyword android jacocoTestCoverageVerification leads me to this page and gets no answer. Finally, I succeeded make the jacoco work and I'd like to share my solution here.

The reason gradle can't find jacocoTestCoverageVerification and jacocoTestReport is because For projects that also apply the Java Plugin, the JaCoCo plugin automatically adds the following tasks jacocoTestReport and jacocoTestCoverageVerification. It means for projects that not appling the java Plugin, it will not add jacocoTestReport and jacocoTestCoverageVerification.

So we have to add them yourself.

Follow the link: Code Coverage for Android Testing, we can add the task jacocoTestReport.

And the same way, we also can add the task jacocoTestCoverageVerification.

Full function goes like

// https://engineering.rallyhealth.com/android/code-coverage/testing/2018/06/04/android-code-coverage.html
ext.enableJacoco = { Project project, String variant ->
    project.plugins.apply('jacoco')

    final capVariant = variant.capitalize()

    StringBuilder folderSb = new StringBuilder(variant.length() + 1)
    for (int i = 0; i < variant.length(); i++) {
        char c = variant.charAt(i)
        if (Character.isUpperCase(c)) {
            folderSb.append('/')
            folderSb.append(Character.toLowerCase(c))
        } else {
            folderSb.append(c)
        }
    }
    final folder = folderSb.toString()


    project.android {
        buildTypes {
            debug {
                testCoverageEnabled true
            }
        }

        testOptions {
            unitTests.all {
                jacoco {
                    //You may encounter an issue while getting test coverage for Robolectric tests.
                    //To include Robolectric tests in the Jacoco report, one will need to set the includeNolocationClasses flag to true.
                    // This can no longer be configured using the android DSL block, thus we search all tasks of Test type and enable it

                    includeNoLocationClasses = true
                }
            }
        }
        jacoco {
            version = '0.8.1'
        }
    }

    project.jacoco {
        toolVersion = '0.8.1'
    }

    project.tasks.create(
            name: 'jacocoTestCoverageVerification',
            type: JacocoCoverageVerification,
            dependsOn: ["test${capVariant}UnitTest",
                        "create${capVariant}CoverageReport"
            ]
    ) {
        onlyIf = {
            true
        }

        violationRules {
            rule {
                limit {
                    minimum = 0.5
                }
            }

            rule {
                enabled = false
                element = 'CLASS'
                includes = ['org.gradle.*']

                limit {
                    counter = 'LINE'
                    value = 'TOTALCOUNT'
                    maximum = 0.3
                }
            }
        }
        def coverageSourceDirs = [
                "src/main/java",
                "src/main/kotlin"
        ]

        def fileFilter = [
                '**/R.class',
                '**/R$*.class',
                '**/*$ViewInjector*.*',
                '**/*$ViewBinder*.*',
                '**/BuildConfig.*',
                '**/*_MembersInjector.class',
                '**/Dagger*Component.class',
                '**/Dagger*Component$Builder.class',
                '**/*Module_*Factory.class',
                '**/*_MembersInjector.class',
                '**/Dagger*Subcomponent*.class',
                '**/*Subcomponent$Builder.class',
                '**/Manifest*.*'
        ]

        def javaClasses = fileTree(
                dir: "${project.buildDir}/intermediates/javac/$folder",
                excludes: fileFilter
        )
        def kotlinClasses = fileTree(
                dir: "${project.buildDir}/tmp/kotlin-classes/$variant",
                excludes: fileFilter
        )

        group = "Reporting"
        description = "Applying Jacoco coverage verification for the ${project.name} with the " +
                "$variant variant."
        classDirectories = files([javaClasses], [kotlinClasses])
        additionalSourceDirs = files(coverageSourceDirs)
        sourceDirectories = files(coverageSourceDirs)
        executionData = fileTree(dir: "${project.buildDir}", includes: [
                "jacoco/testDebugUnitTest.exec",
                "outputs/code_coverage/debugAndroidTest/connected/*.ec",
                "outputs/code_coverage/connected/*.ec" //Check this path or update to relevant path
        ])
    }
    project.tasks.create(
            name: 'jacocoTestReport',
            type: JacocoReport,
            dependsOn: ["test${capVariant}UnitTest",
                        "create${capVariant}CoverageReport"
            ]
    ) {
        def coverageSourceDirs = [
                "src/main/java",
                "src/main/kotlin"
        ]

        def fileFilter = [
                '**/R.class',
                '**/R$*.class',
                '**/*$ViewInjector*.*',
                '**/*$ViewBinder*.*',
                '**/BuildConfig.*',
                '**/*_MembersInjector.class',
                '**/Dagger*Component.class',
                '**/Dagger*Component$Builder.class',
                '**/*Module_*Factory.class',
                '**/*_MembersInjector.class',
                '**/Dagger*Subcomponent*.class',
                '**/*Subcomponent$Builder.class',
                '**/Manifest*.*'
        ]

        def javaClasses = fileTree(
                dir: "${project.buildDir}/intermediates/javac/$folder",
                excludes: fileFilter
        )
        def kotlinClasses = fileTree(
                dir: "${project.buildDir}/tmp/kotlin-classes/$variant",
                excludes: fileFilter
        )

        group = "Reporting"
        description = "Generate Jacoco coverage reports for the ${project.name} with the " +
                "$variant variant."
        classDirectories = files([javaClasses], [kotlinClasses])
        additionalSourceDirs = files(coverageSourceDirs)
        sourceDirectories = files(coverageSourceDirs)
        executionData = fileTree(dir: "${project.buildDir}", includes: [
                "jacoco/testDebugUnitTest.exec",
                "outputs/code_coverage/debugAndroidTest/connected/*.ec",
                "outputs/code_coverage/connected/*.ec" //Check this path or update to relevant path
        ])
        onlyIf = {
            true
        }
        println project
        println "current $project buildDir: $buildDir project buildDir: ${project.buildDir}"
        System.out.flush()
        reports {
            html.enabled = true
            html.destination file("reporting/jacocohtml")
        }
    }


}

Gist version

fangzhzh
  • 2,172
  • 21
  • 25
  • Somehow, if you create tasks like jacocoTestReport(dependsOn: testDebugUnitTest) and jacocoTestCoverageVerification(dependsOn:jacocoTestReport) then jacocoTestCoverageVerification doesn't work like you expected. If i try your solution it works fine. Any ideas? – volsahin Jun 03 '20 at 20:35
0

First of all check you gradle verison: For projects that also apply the Java Plugin, The JaCoCo plugin automatically adds the following tasks:

gradle 3.3 (jacocoTestReport task) https://docs.gradle.org/3.3/userguide/jacoco_plugin.html#sec:jacoco_tasks

gradle 3.5 (jacocoTestReport , jacocoTestCoverageVerification task) https://docs.gradle.org/current/userguide/jacoco_plugin.html#sec:jacoco_tasks

Maybe for earlier versions of gradle you need add jacoco dependencies:

...
dependencies {
сlasspath (
[group: 'org.jacoco', name: 'org.jacoco.agent', version: version: '0.7.7.201606060606'],
[group: 'org.jacoco', name: 'org.jacoco.ant', version: version: '0.7.7.201606060606']
)}
...