Kover 登場前の JaCoCo による Android のローカル単体テストのカバレッジレポート出力は難しかった

この記事は「[Kover でらくらく！ Android のカバレッジレポート出力](https://qiita.com/tfandkusu/items/a6cf3dd42bb699986245)」の補足となります。

> Gradle でカバレッジレポートを出力する設定方法は、Codecov 公式で JaCoCo Plugin を使う方法が紹介されています。しかし Android のローカル単体テストを対象とする場合の設定が難解です。近年、Kotlin 公式のプラグインである Kover がリリースされ、それを使用することで、 最短2行のシンプルな記述でカバレッジレポートの出力を設定できるようになりました。

と言いましたが、[JaCoCo Plugin](https://docs.gradle.org/current/userguide/jacoco_plugin.html) はどのくらい難解だったのかを解説します。

# 開発スタイルおよび環境

この記事では元の記事と同様の開発スタイルおよび環境を想定しています。

- Android プロジェクト
    - マルチモジュールを前提に解説しています。
    - シングルモジュールでもそのまま使えます。
- [ローカル単体テスト](https://developer.android.com/training/testing/local-tests)
    - [Robolectric](https://robolectric.org/) を使ったローカル単体テストにも対応しています。
    - [インストゥルメント化単体テスト](https://developer.android.com/training/testing/instrumented-tests)は今回の対象外です。
- Gradle 8.1.1
- Android Gradle Plugin 8.0.2
- Kotlin 1.8.22

# バイナリ形式のカバレッジレポートを出力する

プロジェクトの build.gradle を編集して設定します。

まず、すべてのモジュールで `jacoco` プラグインを使うようにします。

```build.gradle
allprojects {
    apply plugin: 'jacoco'
}
```

すると単体テストタスク実行時に `testDebugUnitTest.exec` がモジュールごとに出力されるようになります。(Debug の部分は Build Variant)


```sh
./gradlew testDebug
```

```sh
find . -type f -name *.exec
```

```
./app/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./feature/home/usecase/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./feature/home/presentation/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./feature/info/compose/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./feature/info/usecase/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./feature/info/presentation/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./data/repository/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./data/local/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./data/remote/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
./viewCommon/build/outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec
```

このファイルの中身はバイナリ形式となっています。

![スクリーンショット 2023-07-10 4.05.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/81f2e6ec-f7f4-6333-9866-9345299acc18.png)

このままでは人間は読めませんし、Codecov でも非対応のフォーマットとなっています。

https://docs.codecov.com/docs/supported-report-formats

# バイナリ形式のカバレッジレポートを HTML および XML 形式のカバレッジレポートに変換する

Codecov は XML 形式のカバレッジレポートに対応しています。また、Gradle タスクのデバッグや Codecov を使わない運用のために、HTML 形式にも変換します。

そのために JaCoCo プラグインで以下のような変換を行います。

*kt ファイル + class ファイル + exec ファイル ⇒ HTML/XML 形式のカバレッジレポート*

プロジェクトの Gradle ファイルに以下の記載を追加することで実現できます。

```build.gradle

// jacocoReport タスクを作成
tasks.register("jacocoReport", JacocoReport) {
{
    // XML 形式 と HTML 形式の両方のカバレッジレポートを出力する
    reports {
        xml.required.set(true)
        html.required.set(true)
    }
    // 全モジュールの kt ファイルのディレクトリ一覧
    def mySourceDirectories = subprojects.collect { project ->
        "${project.projectDir}/src/main/java"
    }
    //　全モジュールの class ファイルのディレクトリ一覧
    def myClassDirectories = subprojects.collect { project ->
        "${project.buildDir}/tmp/kotlin-classes/debug"
    }
    // exec ファイル一覧
    def execFileTree = fileTree(dir: rootDir, includes: [
            "**/testDebugUnitTest.exec"
    ])
    // それらを JacocoReport タスクの設定値とする
    sourceDirectories.setFrom(mySourceDirectories)
    classDirectories.setFrom(myClassDirectories)
    executionData.setFrom(execFileTree)
}
```

これで `jacocoReport` タスクの実行で XML 形式 と HTML 形式のカバレッジレポートが出力されます。

```sh
./gradlew jacocoReport
```

出力先はこちらです。

XML 形式 `build/reports/jacoco/jacocoReport/jacocoReport.xml` 
HTML 形式 `build/reports/jacoco/jacocoReport/html/index.html`

HTML 形式のレポートはこのように出力されます。

![スクリーンショット 2023-07-15 1.15.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/313bca29-74e7-d6de-7a58-5c8730d6f72b.png)

# jacocoReport タスクだけでカバレッジレポートを出力する

これまでの設定ではカバレッジレポートを出力するために、2つのタスク実行が必要です。

```sh
./gradlew testDebug
./gradlew jacocoReport
```

1タスクで完結するために、タスクの依存関係を設定します。

```build.gradle
tasks.register("jacocoReport", JacocoReport) {
    // 全モジュールの testDebugUnitTest タスク一覧
    def tasks = subprojects.findAll { project ->
        // モジュールでないディレクトリも列挙されるので、build.gradle が存在するものだけを対象とする。
        project.file("build.gradle").exists()
    }.collect { project ->
        "$project.path:testDebugUnitTest"
    }
    // それらが完了してから、このタスクを実行する
    dependsOn(tasks)
    
    // 以下、前の節ど同じ
}
```

これで `jacocoReport` タスクだけでカバレッジレポートが出力されるようになります。

```sh
./gradlew jacocoReport
```

# カバレッジレポート対象クラスを制限する

例えば ViewModel クラスだけなど、カバレッジレポート対象クラスを制限する方法も紹介します。[freeTree メソッド](https://docs.gradle.org/current/userguide/working_with_files.html#sec:file_trees)を使い、対象クラスの class ファイルだけ指定すれば良いです。


```build.gradle

// jacocoReport タスクを作成
tasks.register("jacocoReport", JacocoReport) {
{
    //　全モジュールの対象 class ファイル一覧
    def classFileTrees = subprojects.collect { project ->
        fileTree(dir: "${project.buildDir}/tmp/kotlin-classes/debug",
                includes: ["**/*ViewModel.class"]
        )
    }
    classDirectories.setFrom(classFileTrees)
}
```

# まとめ

この記事では JaCoCo Plugin で Android のローカル単体テストのカバレッジレポート出力の設定方法を解説しました。最短2行で設定が完了する Kover に対して、JaCoCo Plugin では Gradle のタスクを作成したり、kt ファイル、class ファイル、exec ファイルのパスを指定する必要があったりと難解です。すでに設定済みならばともかくとして、これから設定する場合は Kover の方がおすすめです。