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

【Android】jacocoでコードカバレッジを取る

最近UTを書くようになってきたのでカバレッジを取ってみました。Gradleにjacocoのプラグインがあるのでそれを使います。

build.gradleはDroidkaigi2017のbuild.gradleを参考にしました。

apply 'jacoco'
apply plugin: 'com.android.application'

android {
    // Settings for Android...
}

jacoco {
    toolVersion = "0.7.7.201606060606"
}

// A list of directories which should be included in coverage report
def coverageSourceDirs = ['src/main/java']
// 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: ['testUiTestDebugUnitTest']) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled true
        html.enabled true
        csv.enabled false
        xml.destination "${buildDir}/reports/jacoco/jacocoTestReport.xml"
        html.destination "${buildDir}/reports/jacoco/html"
        classDirectories = files(
                fileTree(
                        dir: "${buildDir}/intermediates/classes/uiTest/debug",
                        exclude: coverageExcludeFiles))
    }
    sourceDirectories = files(coverageSourceDirs)
    executionData = files "${buildDir}/jacoco/testUiTestDebugUnitTest.exec"

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

dependencies {
    // For dependencies...
}

まずapply ‘jacoco'でプラグインを適用します。そしてjacocoプラグインのバージョンを指定します。

jacoco {
    toolVersion = "0.7.7.201606060606"
}

バージョンの一覧はここにあります。

次にコードカバレッジのレポートを作るタスクを作ります。 jacocoプラグインにはJacocoReportというレポートを作成するタスクがあります。jacocoの解析対象はバイトコードのため、UTのタスク(test(ProductFlavor)DebugUnitTest)と同時に実行させる必要があるので依存させます。私の場合はuiTestというProductFlavorでUTを実行しているので依存させるタスク名はtestUiTestDebugUnitTestになります。タスク名がわからなかったら./gradlew tasksで調べます。

task jacocoTestReport(type: JacocoReport, dependsOn: ['testUiTestDebugUnitTest']) {
}

あとはJacocoReportのページを参考に設定します。私の場合はActivityやFragment等のクラスはカバレッジに含めないようにしました。先ほど書いたようにjacocoの解析対象はバイトコードのため、reports.classDirectoriesにはビルド後のパスを設定します。executionDataのパスは"${buildDir}/jacoco/test(ProductFlavor)DebugUnitTest.exec"となるようです。

// A list of directories which should be included in coverage report
def coverageSourceDirs = ['src/main/java']
// 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: ['testUiTestDebugUnitTest']) {
    group = "Reporting"
    description = "Generate Jacoco coverage reports after running tests."
    reports {
        xml.enabled true
        html.enabled true
        csv.enabled false
        xml.destination "${buildDir}/reports/jacoco/jacocoTestReport.xml"
        html.destination "${buildDir}/reports/jacoco/html"
        classDirectories = files(
                fileTree(
                        dir: "${buildDir}/intermediates/classes/uiTest/debug",
                        exclude: coverageExcludeFiles))
    }
    sourceDirectories = files(coverageSourceDirs)
    executionData = files "${buildDir}/jacoco/testUiTestDebugUnitTest.exec"
}

結果は↓のような感じ f:id:phicdy:20170625112949p:plain

これでjacocoによるコードカバレッジが取れるようになりました。 次はCircleCIとCodecovを連携させてコードカバレッジGitHubのREADMEに表示する方法について書きたいと思います。