Android test coverage report for multi module app

别来无恙 提交于 2019-12-22 05:20:25

问题


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"
])}

回答1:


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




回答2:


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




回答3:


Instead of using getByPath You can also use variables to reach different modules the build.gradle itself e.g $buildDir will take you to current module build folder.

Secondly, $project.projectDir.parent will take to parent project. Example $project.projectDir.parent/<sub-project-name>/outputs/code-coverage/connected/coverage.ec

You can use your sub project name : gcm_demo, googleservices or networkcommunication as $project.projectDir.parent/gcm_demo/outputs/code-coverage/connected/coverage.ec

Note: make sure you use right file coverage.ec OR coverage.exec check whats being generated for you

To print all the paths, you can use the following task in the build.gradle: run gradle paths as defined as

task paths { println "Printing the current module build: $buildDir" println "Printing the module directory: $project.projectDir" println "Printing the parent module: $project.projectDir.parent" }

This will help you play with directories and file folders in the multi-module android projects. Problem for me was not able reach to the right folders and file directories from the sub-module build.gradle



来源:https://stackoverflow.com/questions/37693566/android-test-coverage-report-for-multi-module-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!