Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android test coverage report for multi module app

We have a multi module application. Where we have 3 library projects and 1 launch project.

module1 (Libraray) module2 (Libraray) depends on module1 module3 (Libraray)depends on module1

Launch (Does not have any source code its just a launcher for all lib)depends on module1 and module 2.

In module1 we are accessing module 2 and module 3 classes using facade pattern. Due to that we need to write all the test cases in Launch project as we have access the to all the classes in launch project so that we have access to all the classes and test cases will not fail due to NoClassDefException.

When we write the test cases in Launch project then we are able to run the test cases and we are getting the execution report as 100% and it create a index.html file with all the details of test cases but when i try to generate the coverage report then it not showing any data for coverage report. Below is my gradle file.

apply plugin: 'com.android.application'
apply plugin: 'jacoco'
android {
compileSdkVersion 22
buildToolsVersion "23.0.2"`

defaultConfig {
    applicationId "com.test.mobile"
    minSdkVersion 14
    targetSdkVersion 17
    multiDexEnabled true
    testApplicationId "com.test.mobile.test"
    testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}

repositories {
    mavenCentral()
}

buildTypes {

    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt'
    }

    debug{
        testCoverageEnabled true
    }
}


dexOptions {
    preDexLibraries = false
    javaMaxHeapSize "4096M"
    jumboMode = true
    incremental false
}

afterEvaluate {
    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
        if (dx.additionalParameters == null) {
            dx.additionalParameters = []
        }
        dx.additionalParameters += '--multi-dex'
        dx.additionalParameters += "--main-dex-list=$projectDir\\multidex-main-dex-list.txt".toString()
    }
}}
dependencies {
compile project(':module2')
compile project(':module3')
compile "com.android.support.test.espresso:espresso-idling-resource:2.2.1"

// Dependencies for local unit tests
testCompile "junit:junit:4.12" exclude group: 'com.android.support', module: 'support-annotations'

testCompile "org.mockito:mockito-all:1.10.19" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.hamcrest:hamcrest-all:1.3" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-module-junit4:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'
testCompile "org.powermock:powermock-api-mockito:1.6.2" exclude group: 'com.android.support', module: 'support-annotations'


// Android Testing Support Library's runner and rules
androidTestCompile "com.android.support.test:runner:0.4.1"  exclude group: 'com.android.support', module: 'support-annotations'
androidTestCompile "com.android.support.test:rules:0.4.1" exclude group: 'com.android.support', module: 'support-annotations'

// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'
androidTestCompile "com.android.support.test.espresso:espresso-contrib:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api' exclude group: 'com.android.support', module: 'support-v4'
androidTestCompile "com.android.support.test.espresso:espresso-intents:2.2.1" exclude group: 'com.google.code.findbugs' exclude group: 'javax.annotation' exclude group: 'com.android.support', module: 'support-annotations' exclude module: 'javax.annotation-api'}

task jacocoTestReport(type: JacocoReport, dependsOn: 'testDebugUnitTest') { def projects = new ArrayList() subprojects.each { prj -> projects.add(prj) }

reports {
    xml.enabled = true
    html.enabled = true
}

jacocoClasspath = configurations['androidJacocoAnt']

def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"

sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
/*sourceDirectories = generateSourceFiles(projects)
classDirectories = generateClassDirs(projects)*/

executionData = files(["${buildDir}/jacoco/testDebugUnitTest.exec",
                       "${buildDir}/outputs/code-coverage/connected/coverage.ec"
])}
like image 734
Hari Soni Avatar asked Jun 08 '16 04:06

Hari Soni


2 Answers

I have 3 modules named with gcm_demo, googleservices and networkcommunication so under build.gradle of each module write

apply plugin: 'jacoco'

task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest', 'createDebugCoverageReport']) {
reports {
    xml.enabled = true
    html.enabled = true
}

def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "${buildDir}/intermediates/classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"

sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = fileTree(dir: "$buildDir", includes: [
        "jacoco/testDebugUnitTest.exec", "outputs/code-coverage/connected/*coverage.ec"
])
} 

Now in Project build.gradle write the following scrpit

apply plugin: 'jacoco'
task jacocoRootReport(type: JacocoReport, dependsOn: ['gcm_demo:jacocoTestReport', 'googleservice:jacocoTestReport', 'networkcommunication:jacocoTestReport']) {
reports {
    xml.enabled = true
    html.enabled = true
}
sourceDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").sourceDirectories,
                           tasks.getByPath("googleservice:jacocoTestReport").sourceDirectories,
                           tasks.getByPath("networkcommunication:jacocoTestReport").sourceDirectories])

classDirectories = files([tasks.getByPath("gcm_demo:jacocoTestReport").classDirectories,
                          tasks.getByPath("googleservice:jacocoTestReport").classDirectories,
                          tasks.getByPath("networkcommunication:jacocoTestReport").classDirectories])

executionData = files([tasks.getByPath("gcm_demo:jacocoTestReport").executionData,
                       tasks.getByPath("googleservice:jacocoTestReport").executionData,
                       tasks.getByPath("networkcommunication:jacocoTestReport").executionData])

}

for execution use

gradlew clean jRR (short abbreviation)

after build successful output folder is

{project location}\build\reports\jacoco\jacocoRootReport\html\index.html

it provides the full project coverage of UI and unitTest

like image 52
Mani Agarwal Avatar answered Sep 28 '22 19:09

Mani Agarwal


This is what we had in our top build.gradle for generating HTML coverage reports:

def coverageSourceDirs = ['app/src/main/java', 'core/src/main/java', 'database/src/main/java']

def coverageExcludes = ['**/R.class',
                        '**/R$*.class',
                        '**/*$$ViewBinder*.*',
                        '**/inject/*',
                        '**/*$InjectAdapter.*',
                        '**/BuildConfig.*',
                        '**/Manifest*.*',
                        '**/Dagger*.*',
                        '**/*_Provide*Factory.*',
                        '**/*_Member*Injector.*',
                        '**/*_Factory.*']

def coverageClassDirectories = [fileTree(dir: 'app/build/intermediates/classes/debug', excludes: coverageExcludes),
                                fileTree(dir: 'core/build/intermediates/classes/debug', excludes: coverageExcludes),
                                fileTree(dir: 'database/build/intermediates/classes/debug', excludes: coverageExcludes)]

task jacocoRootReport(type: JacocoReport) {
    dependsOn "app:jacocoTestReport",
            "core:jacocoTestReport",
            "database:jacocoTestReport"

    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    classDirectories = files(coverageClassDirectories)
    executionData = files(tasks.getByPath("app:jacocoTestReport").executionData,
            tasks.getByPath("core:jacocoTestReport").executionData,
            tasks.getByPath("database:jacocoTestReport").executionData    
)

    reports {
        html.enabled = true
        xml.enabled = false
        csv.enabled = false
    }
    onlyIf = {
        true
    }

    doFirst {
        executionData = files(executionData.findAll {
            it.exists()
        })
    }
}

And in every submodule:

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
            testCoverageEnabled = true
        }
    }
}

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

task jacocoTestReport(type: JacocoReport, dependsOn: "testJenkinsUnitTest") {
    group = "Reporting"

    description = "Generate Jacoco coverage reports"

    classDirectories = fileTree(dir: 'build/intermediates/classes/debug',
            excludes: ['**/R.class',
                       '**/R$*.class',
                       '**/*$$ViewBinder*.*',
                       '**/inject/*',
                       '**/*$InjectAdapter.*',
                       '**/BuildConfig.*',
                       '**/Manifest*.*',
                       '**/Dagger*.*',
                       '**/*_Provide*Factory.*',
                       '**/*_Member*Injector.*',
                       '**/*_Factory.*',
                       '**/PagerTitleStripV22*.*'])

    additionalSourceDirs = files(coverageSourceDirs)
    sourceDirectories = files(coverageSourceDirs)
    executionData = files('build/jacoco/testDebugUnitTest.exec')

    doFirst {
        new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
            if (file.name.contains('$$')) {
                file.renameTo(file.path.replace('$$', '$'))
            }
        }
    }

    reports {
        xml.enabled = false
        html.enabled = true
    }
}

Note that you don't need it if you do coverage analyse with Jenkins plugin or Sonar.

P.S. if you have any problems please check all paths manually, I might mistype something. It is really a pain to setup it

like image 28
Eugen Martynov Avatar answered Sep 28 '22 17:09

Eugen Martynov