Kover でらくらく！ Android のカバレッジレポート出力

# はじめに

Android アプリ開発において、単体テストを作成することはデグレードを防ぐための最も基本的でかつ低コストな施策です。そこにカバレッジレポート出力があると、単体テストによってカバーされている行とされていない行を視覚的に把握することができ、単体テストの作成とコードレビューをより正確かつ効率的に行うことができます。

## カバレッジレポートをチームで共有

この記事では、GitHub のプルリクにコメントとしてカバレッジレポートが投稿される設定を行うことで、それをチームで共有できるようにする設定を紹介します。

![スクリーンショット 2023-07-03 3.27.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/3ce7239b-4b08-bd7f-f9c1-14683bd796f0.png)

![スクリーンショット 2023-07-03 3.32.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/fd91b086-c0dc-4a35-233d-4b6147e0dca3.png)

## 複数記事を予定しています

Android アプリ開発には様々な開発スタイルがあり、それらをすべて網羅してカバレッジレポートを解説すると長大な記事になるため、今回は基本的な内容に留めて、今後テーマを分けて複数記事の投稿を予定しています。

現在、以下の記事を公開しています。

https://qiita.com/tfandkusu/items/32ed94334e0ff0b18d63

https://qiita.com/tfandkusu/items/94738a7ad695fa59b9e7

## 記事の構成

今回はカバレッジレポートをチームで共有するための手順を以下の構成で解説しています。

1. Gradle でカバレッジレポートを出力する
2. [Codecov](https://about.codecov.io/) と GitHub を連携する
3. リポジトリに Codecov のための設定ファイルを置く
4. CI で Codecov にカバレッジレポートをアップロードする

Codecov を使わずに HTML 形式のカバレッジレポートを出力して運用するケースは、こちらの記事で解説しています。

https://qiita.com/tfandkusu/items/32ed94334e0ff0b18d63

## 設定が簡単になった

Gradle でカバレッジレポートを出力する設定方法は、Codecov 公式で [JaCoCo Plugin](https://docs.gradle.org/current/userguide/jacoco_plugin.html) を使う方法が紹介されています。しかし Android のローカル単体テストを対象とする場合の設定が難解です。 近年、Kotlin 公式のプラグインである [Kover](https://github.com/Kotlin/kotlinx-kover) がリリースされ、それを使用することで、 最短2行のシンプルな記述でカバレッジレポートの出力を設定できるようになりました。
また、以前からある Jacoco の設定方法はこちらの記事で解説しています。

https://qiita.com/tfandkusu/items/94738a7ad695fa59b9e7

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

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

- Android プロジェクト
    - シングルモジュールだけでなくマルチモジュールにも対応しています。
- [Kotlin Multiplatform Mobile](https://kotlinlang.org/lp/multiplatform/) プロジェクトでの設定方法は、最後に補足として紹介しています。
- [ローカル単体テスト](https://developer.android.com/training/testing/unit-testing/local-unit-tests)
    - [Robolectric](https://robolectric.org/) を使ったローカル単体テストにも対応しています。
    - [インストゥルメント化単体テスト](https://developer.android.com/training/testing/instrumented-tests)は Kover を使いませんが別の記事で解説する予定です。
- Gradle 8.1.1
- Android Gradle Plugin 8.0.2
- Kotlin 1.8.22

# Gradle でカバレッジレポートを出力する

GitHub Actions 等の CI でカバレッジレポートを出力するために Gradle の設定を行います。

## 公式の設定例

設定方法は公式の GitHub リポジトリの kover-gradle-plugin/examples フォルダを参考にすれば良いです。

https://github.com/Kotlin/kotlinx-kover/tree/main/kover-gradle-plugin/examples

以下のような開発スタイルに対応した設定例が紹介されています。(一部抜粋)

| フォルダ名 | モジュール | Gradle DSL の言語 |
| --- | --- | --- |
| [android/minimal_groovy/](https://github.com/Kotlin/kotlinx-kover/tree/main/kover-gradle-plugin/examples/android/minimal_groovy) | シングルモジュール | Groovy |
| [android/minimal_kts/](https://github.com/Kotlin/kotlinx-kover/tree/main/kover-gradle-plugin/examples/android/minimal_kts) | シングルモジュール | [Kotlin](https://docs.gradle.org/current/userguide/kotlin_dsl.html) |
| [android/multiproject/](https://github.com/Kotlin/kotlinx-kover/tree/main/kover-gradle-plugin/examples/android/multiproject) | マルチモジュール | Kotlin |

## Kover のミニマムな設定方法

公式の設定例を参考にミニマムな設定をします。
まずプロジェクトの build.gradle に以下の設定を追加して、プロジェクト全体で Kover を使えるようにします。

```build.gradle
plugins {
    id 'org.jetbrains.kotlinx.kover' version '0.7.2' apply false
}
```

次にモジュールの build.gradle の plugins ブロックに以下の設定を追加します。

```app/build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    // 追加
    id 'org.jetbrains.kotlinx.kover'
}
```

[以前の書き方](https://docs.gradle.org/current/userguide/plugins.html#sec:old_plugin_application)をお使いの場合は、こちらの設定を追加します。


```app/build.gradle
apply plugin: "org.jetbrains.kotlinx.kover"
```

動作確認のために、以下の Gradle タスクを実行します。`Debug` の部分はビルドタイプになります。

```sh
./gradlew koverHtmlReportDebug
```

ローカル単体テストが実行され、HTML 形式のカバレッジレポートの保存パスを標準出力されます。

```
Kover: HTML report for ':app' file:///Users/takada/work/android_app_template/app/build/reports/kover/htmlDebug/index.html
```

そのパスを Web ブラウザで開くと、このように表示されます。

![スクリーンショット 2023-07-03 3.16.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/7051f8ed-766b-ff54-f890-e04abf15533e.png)

パッケージ名やファイル名のリンクを辿ることで、個別 Kotlin ファイルのカバレッジを確認できます。

![スクリーンショット 2023-07-03 2.56.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/246faa31-6e45-d929-3486-92f2a7a922b9.png)

これだけの設定では、BuildConfig ファイルや [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) によって自動生成されたクラスも表示されてしまいます。しかし Codecov で使う分には、いったん気にしなくて良いです。最終的には GitHub リポジトリで管理されているコードのみのカバレッジレポートになります。Codecov を使わずに HTML 形式のカバレッジレポートを運用する場合や ViewModel など特定種類のクラスに限定したカバレッジレポート出力を行う方法はこちらの記事で解説しています。

https://qiita.com/tfandkusu/items/32ed94334e0ff0b18d63

## マルチモジュールの場合

マルチモジュールの場合はカバレッジレポートを出力したいすべてのモジュールで  `org.jetbrains.kotlinx.kover` プラグインを使えるようにします。

```feature/home/presentation/build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    // 追加
    id 'org.jetbrains.kotlinx.kover'
}
```

すでに [Script Plugin](https://docs.gradle.org/current/userguide/plugins.html#sec:script_plugins) 等で複数モジュールの設定を共通化している場合は、そこに `org.jetbrains.kotlinx.kover` プラグインを使える設定を追加すれば良いです。

そしてアプリケーションモジュールの dependencies ブロックに、以下のようにカバレッジレポートを出力したいモジュール一覧を設定します。直接参照していないモジュールも設定可能です。


```app/build.gradle
dependencies {
    // 省略

    // 追加
    kover(project(':viewCommon'))
    kover(project(':feature:home:presentation'))
    kover(project(':feature:info:presentation'))
    // こちらは直接参照していない2階層下のモジュール群
    kover(project(':feature:info:usecase'))
    kover(project(':feature:home:usecase'))
}
```

# Codecov と GitHub を連携する

ここまでの手順で Gradle で Kover を設定したことで、カバレッジレポートを HTML ファイルとして出力できるようになりました。次は Codecov と GitHub を連携することで、プルリクのコメントにカバレッジレポートを投稿する設定を行います。

まず Codecov に GitHub アカウントでログインします。

https://about.codecov.io/

![スクリーンショット 2023-07-03 3.41.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/20eb663d-180c-ff41-8a42-77dd5304b92b.png)

カバレッジレポートを出力したいリポジトリの `setup repo` をクリックします。

![スクリーンショット 2023-07-03 3.47.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/0eb7ac3e-5017-0703-6daf-4e837b9f437f.png)

CODECOV_TOKEN が取得できます。流出しないように注意してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/97acf230-7f6f-035e-057f-be9d14b35b28.png)

次に CI の設定を行いますが、 GitHub Actions をお使いの場合は Actions Secrets に CODECOV_TOKEN を設定します。( リポジトリ → Settings → Secrets and variables → Actions)

![スクリーンショット 2023-07-03 4.01.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/ae7a38e2-e1d4-60eb-d2b9-d822b207b53e.png)

## Codecov アプリケーションを GitHub に追加する

Codecov のユーザアイコンから Install codecov app を選択して、Codecov アプリケーションを GitHub にインストールします。

![スクリーンショット 2023-07-03 4.52.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/25897ae0-e3a8-2623-66b0-7074df01f3d3.png)

![スクリーンショット 2023-07-03 5.04.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/8507b896-21d8-f193-e662-35b4692af168.png)

# リポジトリに Codecov のための設定ファイルを置く

Android プロジェクトのリポジトリ直下に以下の内容で `codecov.yml` ファイルを設置します。

```codecov.yml
coverage:
  status:
    project: off
    patch: off
```

このファイルでカバー率が何パーセント以上下がったらCIを失敗にする設定が出来ますが、今回はカバレッジレポートの表示のみを行いたいので無効化します。

# CI で Codecov にカバレッジレポートをアップロードする

## GitHub Actions をお使いの場合

GitHub Actions をお使いの場合は、PUSH ごとに行われるワークフローに以下の2ステップを追加すれば良いです。

```.github/workflows/check.yml
name: check
on:
  push:
    branches:
      - main
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
jobs:
  check:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-java@v3
        with:
          distribution: 'adopt'
          java-version: '17'
      - uses: gradle/gradle-build-action@v2
      # 追加
      # 単体テストの実行 + XML 形式のカバレッジレポート出力
      - run: ./gradlew koverXmlReportDebug
      # Codecov にカバレッジレポートをアップロード
      - uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          # アプリケーションモジュールの XML 形式のカバレッジレポートのパスを指定
          files: ./app/build/reports/kover/reportDebug.xml
          fail_ci_if_error: true
```

手元の実行では `koverHtmlReportDebug` タスクを実行していましたが、CI では `koverXmlReportDebug` を実行します。Codecov でサポートされているカバレッジレポートファイルの形式が HTML でなく XML だからです。

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

## GitHub Actions 以外をお使いの場合

GitHub Actions 以外の CI をお使いの場合は Codecov Uploader を使用します。

https://docs.codecov.com/docs/codecov-uploader

CI のステップに以下のシェルスクリプトを追加します。
CODECOV_TOKEN が環境変数に設定されていることが前提です。

```sh
curl -Os https://uploader.codecov.io/latest/linux/codecov
chmod +x codecov
./codecov -t ${CODECOV_TOKEN} -f ./app/build/reports/kover/reportDebug.xml
```

# まとめ

この記事では Android プロジェクトで Kover を用いてカバレッジレポートを出力する Gradle タスクの設定方法と Codecov を使ってプルリクでカバレッジレポートをチームで共有する設定を紹介しました。
今回は基本的な内容のみを説明しましたが、今後、Android アプリの様々な開発スタイルに対応した応用的な内容を説明する予定です。

# 補足

## kotlin Multiplatform Mobile プロジェクトの場合

kotlin Multiplatform Mobile プロジェクトの場合でも設定方法は同じです。注意するべき点としては、JVM で動作する単体テストのみカバレッジレポートが出力されるという点です。Android / iOS のエミュレータ/シミュレーター/実機で動作する単体テストには対応していません。

https://github.com/Kotlin/kotlinx-kover/issues/96

また、Kotlin Multiplatform モジュールで JVM 向けのビルドを追加し、JVM だけで動作する単体テストでカバレッジを担保している場合は、Kover のタスク名に Android のビルドタイプは付けないです。

```shared/build.gradle.kts
kotlin {
    // 略
    android {
        compilations.all {
            kotlinOptions {
                jvmTarget = "17"
            }
        }
    }
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach {
        it.binaries.framework {
            baseName = "shared"
        }
    }
    // JVM 向けビルドを追加
    jvm()

    // 略
}
```

```sh
# こちらのコマンドでカバレッジレポートが出力される
./gradlew koverHtmlReport
./gradlew koverXmlReport
```

JVM だけで動作する単体テスト書く例としては [Mockk](https://github.com/mockk/mockk) を使う代わりに iOS での単体テスト実行を諦めたケースが該当します。

![スクリーンショット 2023-07-16 19.59.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/40f55390-0bfc-6cac-1a96-8c8f7cd05136.png)