skip to content
私的歌詞倉庫

octocovとKoverを使ってテストカバレッジをPull Requestにコメントする

/ 8 min read

Updated:
Table of Contents

はじめに

こんにちは、最近は新しい職場でAndroid開発をメインにお仕事しています。

その新しい職場でみんなでチーム目標を決める時に、ユニットテストをもっと書きたいねという話がでました。

どうすればテストを書くのかなと考えた時に、前職のRailsプロジェクトではoctocovを使って、カバレッジが低いとマージさせない仕組みがあったので、それをAndroidプロジェクトに応用できないかと思い、試しに個人で開発しているKMP(Kotlin Multi Platform)プロジェクトで試してみました。

色々試してある程度分かってきたので設定方法について紹介します。使用したコードはGitHub上に公開しています。

GitHub - Tatsumi0000/nemomemo

octocovとKover

今回使用するoctocovとKoverについて説明します。

cotocov

octocovはコードのメトリクス(カバレッジや実行時間など…)を収集してくれるGo製のツールです。

GitHub - k1LoW/octocov: octocov is a toolkit for collecting code metrics (code coverage, code to test ratio, test execution time and your own custom metrics).

CLIとしても使えるのですが、今回はGitHub Actions上で使うために公式で準備されているoctocov-actionを使います。

GitHub - k1LoW/octocov-action: :octocat: GitHub Action for octocov

Kover

KoverはKotlin公式のテストカバレッジを収集するツールです。

GitHub - Kotlin/kotlinx-kover

ちょっと前だとJaCoCoを使ってテストカバレッジを収集したりすることも多かったみたいですが、今回は公式のツール & 設定が簡単らしいという理由でKoverを使うことにしました。

octocovは色々なカバレッジのフォーマットに対応していて、JVM系だとJaCoCo形式のフォーマットに対応しています。なぜ、JaCoCoではなくKoverで良いかというと、実はKoverではXML形式でカバレッジを出力すると、そのXMLがJaCoCoフォーマットになっているのでKoverで大丈夫という感じです。

実装

方針としてはconvention pluginとしてKoverを登録しそこからプロジェクト全体に適用します。

まずはKover関連のライブラリを追加します。使用するバージョンは0.9.1になります。

0.7.xから0.8.3に上がったタイミングで設定方法に変更があるので、他記事などを参考にする時はバージョンに注意して下さい(公式のマイグレーションガイドもあるのでそちらも参考になります)。

kotlinx-kover/kover-gradle-plugin/docs/migrations/migration-to-0.8.0.md at main · Kotlin/kotlinx-kover

Version Catalogに追加し、convention pluginで読み込みます。

[versions]
kover = "0.9.1"
[libraries]
koverGradlePlugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
[plugins]
kotlinxKover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
[bundles]
plugins = [
"koverGradlePlugin"
]
dependencies {
implementation(libs.bundles.plugins)
}

プロジェクト全体にセットしたいのでプロジェクト直下で読み込みます。

plugins {
alias(libs.plugins.kotlinxKover) apply false
}

KoverのカスタムGradleプラグインを作ります。ほぼDroidKaigi2024のままです。

class KoverPlugin: Plugin<Project> {
override fun apply(target: Project) {
val koverPlugin = "org.jetbrains.kotlinx.kover"
with(target) {
pluginManager.apply(koverPlugin)
// プロジェクト内の各モジュールに適用するためサブプロジェクトが呼ばれる前にKoverをapplyする。
rootProject.subprojects {
if (this@subprojects.name == target.name) return@subprojects
this@subprojects.beforeEvaluate {
this@subprojects.pluginManager.apply(koverPlugin)
}
target.dependencies.add("kover", this@subprojects)
}
extensions.configure<KoverProjectExtension>("kover") {
reports {
total {
xml {
// カバレッジのレポートをわかりやすい場所に出力します(rootのプロジェクト直下)。
xmlFile.set(rootProject.file("coverage.xml"))
}
}
filters {
// 評価されてほしくないコードを除外します(自動生成系)。
excludes {
classes(
// classes generated by Hilt
"Hilt_*",
"*_Factory",
"*_HiltModules*",
"*Module_Provide*Factory",
// compose previews
"*Preview*Kt",
)
}
}
}
}
}
}
}

Koverの設定はできたのでこれをカスタムGradleプラグインとして登録します。

gradlePlugin {
plugins {
register("kover") {
id = "love.aespa.nemomemo.kover"
implementationClass = "KoverPlugin"
}
}
}

ここがいまいちよく分かってないのですが、DroidKaigi2024だとメインのアプリモジュールにのみ適用していたのでcomposeAppにのみ設定を追加しています(おそらくKoverのカスタムGradleプラグインを作った時に、サブプロジェクトにも勝手に適用するようにしたので、メインのモジュールにだけ適用しているということかな…と勝手に解釈しています)。

plugins {
id("love.aespa.nemomemo.kover")
}

ここまでの設定で./gradlew tasks を実行するとkoverXmlReport のタスクが追加されていると思います。このタスクを実行するとプロジェクト直下にcoverage.xml が出力されるのでこのファイルをoctocovで読み込むようにします。

次にoctocovの設定です。プロジェクト直下に.octocov.ymlを作成します。色々設定があるのですが、今回は以下のように設定しました。

coverage:
acceptable: 10% ## カバレッジが10%以上だったらOK
paths:
- coverage.xml ## カバレッジレポートのパス
diff: ## 差分を比較
datastores:
- artifact://${GITHUB_REPOSITORY}
comment: ## PRでコメントする
if: is_pull_request
report: ## デフォルトブランチでデータを取得後GitHubのartifactに保存し差分を比較する
if: is_default_branch
datastores:
- artifact://${GITHUB_REPOSITORY}
summary: ## CIのsummaryに実行結果を出力します
if: true

CIで動かすためにGitHub Actionsのワークフローを追加します。

name: Test
on:
pull_request:
push:
branches:
- main
permissions:
pull-requests: write
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
cache: "gradle"
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: UnitTest
run: ./gradlew allTests
- name: Generate Coverage Report
run: ./gradlew koverXmlReport
- uses: k1LoW/octocov-action@v1

注意点は、permissionspull-requests: write です。これがないとoctocovがPull Requestにコメントをする権限が足りずに、CI上で403になりコメントされません(CI自体はエラーになりません)。

Skip commenting report to pull request: POST https://api.github.com/repos/Tatsumi0000/nemomemo/issues/2/comments: 403 Resource not accessible by integration []

CIが動くとPull RequestとCIのsummaryにコメントをします。

Pull Requestにコメント

Pull Requestにコメント

CIのsummaryにコメント

CIのsummaryにコメント

もし.octocov.ymlacceptableに満たない場合はCIが失敗します。

acceptableを60%にしているのでCIが落ちる

acceptableを60%にしているのでCIが落ちる

終わりに

octocovとKoverを使ってカバレッジのレポートを表示するようにしました。

初めてoctocovとKoverの設定を書いたのですが、非常に簡単に使うことができたので良かったです。

導入方法は分かったので実際に業務でも試してみたいなと思います!

参考文献