Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Vol.03 Kover CLIでKotlinのTest Coverage Reportをあとからマージする

こんにちは。2024年4月に新卒として入社しました、技術本部Strategic Products Engineering Unit Contract One Devグループの髙野です。 2023年3月に内定者インターンを開始して以来、契約データベース「Contract One」を開発しています。

本記事では、自動テストの改善活動の一環として、Gradleのマルチプロジェクト構成でTest Coverage Reportを生成するために調査したことについてまとめます。

なお、本記事は【Strategic Products Engineering Unitブログリレー】という連載企画の記事です。

buildersbox.corp-sansan.com

背景

なぜTest Coverageを取るのか

Contract OneではTest Coverageが可視化されておらず、自動テストの網羅率が明確になっていません。 そのため、ライブラリをバージョンアップする際に「CIが通れば大丈夫」と自信を持って言えず、手動で広範囲にわたる動作確認をする必要があります。

Test Coverageを測定し、自動テストの網羅率を把握することで、ライブラリのバージョンアップをより安心して行えるようになります。また、既存のコードに変更を加える際のリスクが減り、新しく追加されるコードの品質も向上すると考えています。

プロジェクト構成

Contract Oneのバックエンドは、Gradleのマルチプロジェクト構成になっています。機能ごとにサブプロジェクトが切られており、それらで共通して使う処理をsharedプロジェクトに置いています。

project-root            # 
├─ app-1                # 機能1
│   ├─ src              # 
│   └─ build.gradle.kts # 
├─ app-2                # 機能2
│   ├─ src              # 
│   └─ build.gradle.kts # 
├─ shared               # 共通処理
│   ├─ src              # 
│   └─ build.gradle.kts # 
├─ gradlew              # 
├─ gradlew.bat          # 
└─ settings.gradle.kts  # 

各機能のサブプロジェクトは、sharedプロジェクトのみに依存させています。 CIでは、サブプロジェクト毎に並列で自動テストを回すことによって、プロジェクト全体の自動テストが回り切るのにかかる時間を抑えています。

Test Coverage Reportの作成

今回は、Test Coverage Reportの作成に、Kotlinが公式で提供しているライブラリ「Kover」を使用します。

github.com

自動テストを並列で回す構成だと、Test Coverage Reportをサブプロジェクト毎にしか生成できず、プロジェクト全体のCoverageを見られません。そこで、Kover CLIを使い、各サブプロジェクトの自動テストジョブで生成したTest CoverageのBinary Reportをあとからマージすることで、プロジェクト全体のTest Coverage Reportを作成します*1

Kover CLIのartifactを用意

Kover CLIの導入はこちらのIssueを参考にしました。 まず、Kover CLIをMavenからダウンロードし、プロジェクトのルートに保存します。記事公開時点の最新版は0.8.0です。

各プロジェクトにpluginを追加

Test Coverageを計測したいサブプロジェクトのbuild.gradle.ktsに、Koverを追加します。複数のプロジェクトで一貫したバージョンを利用するため、今回はGradleのVersion Catalogという機能を使用します。

// project-root/settings.gradle.tks
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            plugin("kover", "org.jetbrains.kotlinx.kover").version("0.8.0")
        }
    }
}
// project-root/{sub-project}/build.gradle.kts
plugins {
    // add
    alias(libs.plugins.kover)
}

自動テストの実行

各サブプロジェクトで自動テストを実行します。終了時に、Binary Reportが project-root/{sub-project}/build/kover/bin-reports/test.icに生成されます。

生成されたBinary Reportのマージ

公式ドキュメントを参考に、Kover CLIでBinary Reportをマージします。 マージには、Binary Reportファイル(test.ic)、コンパイルされたClassファイル(*.class)、Sourceファイル(*.kt)の3つが必要です*2

サブプロジェクトが増えた時のために、マージの処理をシェルスクリプトに書き起こしました。(GitHub Copilotにほとんど書いてもらいました。)

#!/bin/sh
# Merge対象のサブプロジェクト名を指定
modules=(
    "share"
    "app-1"
    "app-2"
)

# Binary Reportsの場所を返す
function create_ic_path() {
  local ic_path=""
  for module in ${modules[@]}; do
    ic_path="${ic_path} ./${module}/build/kover/bin-reports/test.ic"
  done
  echo ${ic_path}
}
# Classの場所を返す
function create_classfiles_path() {
  local classfiles_path=""
  for module in ${modules[@]}; do
    classfiles_path="${classfiles_path} ./${module}/build/classes/kotlin/main"
  done
  echo ${classfiles_path}
}
# Sourceの場所を返す
function create_src_path() {
  local src_path=""
  for module in ${modules[@]}; do
    src_path="${src_path} ./${module}/src"
  done
  echo ${src_path}
}

# 出力先のディレクトリ
out_dir="./coverage-report"
# 以前の出力が存在したら消す
rm -rf ${out_dir}
# Test Coverage ReportをHTML形式で出力する
java -jar kover-cli.jar report $(create_ic_path) --classfiles $(create_classfiles_path) --src $(create_src_path) --html ${out_dir}

シェルスクリプトを実行すると、coverage-reportディレクトリにTest Coverage Reportが出力されます。

Test Coverage Reportの出力結果

おわりに

Gradleのマルチプロジェクト構成で、プロジェクト全体のTest Coverage Reportを生成する方法についてまとめました。 漠然としていた既存の自動テストの網羅率を、数字としてはっきりと取れるようになり、コードの品質向上に向けた取り組みを考えやすくなったと思います。

内定者インターンをやっていた頃から、Test Coverageを切り口としてコードの品質を向上できるのではないかと考えていました。現状のTest Coverageを簡単に知れるようになり、わくわくしています。

*1:Kover Gradle Pluginには、依存しているプロジェクトの自動テストをすべて回してTest Coverage Reportをまとめて生成できる機能がありますが、自動テストの並列化と相性が悪く今回は採用しませんでした。

*2:CIでマージを行う場合、並列で実行したそれぞれの自動テストジョブからファイルを回収する必要があります。

© Sansan, Inc.