こんにちは。2024年4月に新卒として入社しました、技術本部Strategic Products Engineering Unit Contract One Devグループの髙野です。 2023年3月に内定者インターンを開始して以来、契約データベース「Contract One」を開発しています。
本記事では、自動テストの改善活動の一環として、Gradleのマルチプロジェクト構成でTest Coverage Reportを生成するために調査したことについてまとめます。
なお、本記事は【Strategic Products Engineering Unitブログリレー】という連載企画の記事です。
背景
なぜ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」を使用します。
自動テストを並列で回す構成だと、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が出力されます。
おわりに
Gradleのマルチプロジェクト構成で、プロジェクト全体のTest Coverage Reportを生成する方法についてまとめました。 漠然としていた既存の自動テストの網羅率を、数字としてはっきりと取れるようになり、コードの品質向上に向けた取り組みを考えやすくなったと思います。
内定者インターンをやっていた頃から、Test Coverageを切り口としてコードの品質を向上できるのではないかと考えていました。現状のTest Coverageを簡単に知れるようになり、わくわくしています。