AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

Androidのマルチモジュールでのjacoco設定

ハマったのでまとめておく。結論から言うとjacocoにアップロードしてGitHubのPR連携をする場合はレポートをマージする必要はない。マージしてしまうとパスを判別できなくなるせいかエラーになってしまう。

f:id:phicdy:20190624224848p:plain

CircleCIで bash <(curl -s https://codecov.io/bash) としていれば勝手に複数のレポートをアップロードしてくれる。

f:id:phicdy:20190624225114p:plain

一方で自分でJenkinsなどを立てている場合はマージしてファイルを1つにしないとグラフ表示などの設定がうまくいかない。

マージする場合

build.gradle

buildscript {
...
    ext.jacoco_version = "0.8.2"
...
}

apply plugin: 'jacoco'

jacoco {
    toolVersion = jacoco_version
}

task jacocoMerge(type: JacocoMerge) {
    gradle.afterProject { p, state ->
        if (p.rootProject != p && p.plugins.hasPlugin('jacoco')) {
            executionData file("${p.buildDir}/jacoco").listFiles().findAll {
                it.name.endsWith(".exec")
            }
        }
    }
}

def coverageExcludeFiles = ['**/R.class', '**/R$*.class', '**/com/android/**/*.*',
                            '**/BuildConfig.class', '**/*Activity*.class',
                            '**/*Fragment*.class', '**/*Receiver.class',
                            '**/*Manifest*.class', '**/*Application*.class',
                            ....]
task jacocoMergedReport(type: JacocoReport, dependsOn: [tasks.jacocoMerge]) {
    executionData jacocoMerge.destinationFile
    def sources = []
    subprojects.forEach {
        sources += "${it.projectDir.path}/src/main/java"
    }
    sourceDirectories = files(sources)
    classDirectories = fileTree(
            dir: ".",
            includes: ["**/build/intermediates/classes/debug/**",
                       "**/build/tmp/kotlin-classes/debug/**"],
            excludes: coverageExcludeFiles
    )

    reports {
        xml.enabled = true
        html.enabled true
        csv.enabled false
        xml.destination file("${buildDir}/reports/jacoco/report.xml")
        html.destination file("${buildDir}/reports/jacoco/html")
    }
}

task testDebugUnitTest(dependsOn: [tasks.jacocoMergedReport])

マージしない場合

phicdy.hatenablog.com phicdy.hatenablog.com

辺りの設定を各モジュールに設定すればOK。

具体的には↓みたいな設定を jacoco.gradle としてプロジェクトのルートに置く。

apply plugin: 'jacoco'

jacoco {
    toolVersion = jacoco_version
}

// A list of directories which should be included in coverage report
def coverageSourceDirs = ['src/main/java', 'src/main/kotlin']
// A list of files which should be excluded from coverage report since they are generated and/or framework code
def coverageExcludeFiles = ['**/R.class', '**/R$*.class', '**/com/android/**/*.*',
                            '**/BuildConfig.class', '**/*Activity*.class',
                            '**/*Fragment*.class', '**/*Receiver.class',
                            '**/*Manifest*.class', '**/*Application*.class',
                            ...]

task jacocoTestReport(type: JacocoReport, dependsOn: ['testDebugUnitTest']) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled true
        html.enabled true
        csv.enabled false
        xml.destination new File("${buildDir}/reports/jacoco/jacocoTestReport.xml")
        html.destination new File("${buildDir}/reports/jacoco/html")
        classDirectories =
                fileTree(dir: "${buildDir}/intermediates/classes/debug",
                        exclude: coverageExcludeFiles) +
                        fileTree(dir: "$buildDir/tmp/kotlin-classes/debug",
                                excludes: coverageExcludeFiles)
    }
    sourceDirectories = files(coverageSourceDirs)
    executionData = files "${buildDir}/jacoco/testDebugUnitTest.exec"

    doLast {
        println "jacoco xml report has been generated to file://${buildDir}/reports/jacoco/report.xml"
        println "jacoco html report has been generated to file://${reports.html.destination}/index.html"
    }
}

そしてjacocoを使いたいモジュールで apply from: "$rootDir/jacoco.gradle" するかもしくはルートのbuild.gradleで適用させてしまうと楽。

subprojects {
    if (name == 'app') {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-kapt'
    apply from: "$rootDir/jacoco.gradle"
    ...
}

参考

vividcode.hatenablog.com

clash-m45.hatenablog.com

github.com