Androidのマルチモジュールでのjacoco設定
ハマったのでまとめておく。結論から言うとjacocoにアップロードしてGitHubのPR連携をする場合はレポートをマージする必要はない。マージしてしまうとパスを判別できなくなるせいかエラーになってしまう。
CircleCIで bash <(curl -s https://codecov.io/bash)
としていれば勝手に複数のレポートをアップロードしてくれる。
一方で自分で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" ... }