はじめに
NX開発推進部プロダクト開発第1G の杉山です。 Androidエンジニアとして日々開発を行っております。 ここ一ヶ月の週末の内、名古屋を3往復もしており、一体どこ住みなのかと皆に聞かれている次第です。
そろそろ名古屋に別荘でも持つべきかと悩んでいます。
そんなことはさておき、近年社内の取り組みとしてアプリの品質改善を強化しており、リリース前のテストはもちろんのこと日々の開発の中でUT(ユニットテスト)の拡充を行っております。 今回はそのUTのカバレッジをGitHub Actionsを使ってブランチごとに計測し、差分を算出できるようにしました。
背景
差分を算出しようと考えた背景として、UTの作成が十分に意識されず、見逃されたり後回しにされたりする現状があります。この状況を改善し、UT作成への意識をより強化したいと考えました。以前から、PR時にカバレッジの計測は行われており、マージ後のカバレッジを把握することは可能でした。しかし、個々のPRによって具体的にどの程度カバレッジが上昇したのかを把握することができず、UTの実装が不十分だった場合にPRの段階で検出できるようにはなっていませんでした。そのため、GitHub Actionsを用いてこれを改善し、実装しました。
実現したいこと
今回実現したいことは以下の3つになります。
- PR対象のターゲットブランチ(developブランチ)とソースブランチ(featureブランチ)それぞれのカバレッジを算出する
- 各ブランチのカバレッジをファイルに保存して参照できるようにする
- 各ファイルを用いてカバレッジの差分算出をする
上記を実現することで、PRの段階でカバレッジの差分を見てUTの実装ができているかどうかを確認することができ、UT作成への意識付けをすることができます。では、それぞれの実装を紹介していきます。
GitHub Actionsの構成
今回実装したjobの構成は以下になっています。
- check-coverage-develop
- developブランチのカバレッジ計測job
- check-coverage-feature
- featureブランチのカバレッジ計測job
- compare-coverage
- カバレッジ差分計測job
Koverによるカバレッジ計測
カバレッジを計測するにあたってKoverを使用しています。KoverはKotlin 公式のプラグインでKotlinに特化したコードカバレッジツールです。Gradleプラグインに組み込むことができ、クラスや関数レベルで詳細なカバレッジレポートを生成することができます。HTMLとXML形式でレポートを生成することができ、今回はXML形式で出力したものを差分計算に利用しています。 導入に関しては公式のドキュメントがあるため今回は割愛します。
今回導入するプロジェクトではコードはMVVMとクリーンアーキテクチャの設計を取り入れており、ロジックの部分の大半はUseCaseクラスに集約されています。そのためUTに対して特に力をいれる必要のあるクラスになっています。 そこで、今回はUseCaseクラスを対象にカバレッジを計測する設定にしています。(設定については以下に記載します)
kover {
currentProject {
createVariant("usecase") {
add(["developDebug"], false)
}
}
// Usecase用のレポート
reports {
variant("usecase") {
// Usecase用のカバレッジフィルター設定
filters {
includes {
classes("*UseCase") // UseCaseクラスのカバレッジ
}
excludes { // Hiltクラスなど自動生成されるクラスは対象から外しています
classes(
"*.BuildConfig",
"hilt_aggregated_deps.*", "dagger.*", "*_Factory", "*_*Factory*",
"*.Hilt_*", "*_HiltModules*",
"*_Impl*"
)
}
}
}
}
}
実装1:ブランチ毎にカバレッジを計測する
ブランチ毎にカバレッジを計測するにはjobをそれぞれのブランチを対象に実行条件を切り分ける必要があります。これは
ifを用いることで、実現することができます。
- ターゲットブランチ
check-coverage-develop:
runs-on: [ ubuntu-latest ]
if: github.base_ref == 'develop' # github.base_ref:マージ先がdevelopブランチであること
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: develop # checkout対象をdevelopに指定
token: ${{ secrets.APP_BOT_GITHUB_TOKENS }}
- ソースブランチ
check-coverage-feature:
runs-on: [ ubuntu-latest ]
if: startsWith(github.head_ref, 'feature/') # github.head_ref:マージ元がfeatureブランチであること
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # checkout対象をソースブランチに指定
token: ${{ secrets.APP_BOT_GITHUB_TOKENS }}
実装2:各ブランチのカバレッジをファイルに保存する
job毎で内容がリセットされてしまい、このままでは計測したカバレッジが保持できず
compare-coverageでカバレッジ差分を計測することができません。
そのため
check-coverage-develop,check-coverage-featureのそれぞれで生成したレポートファイルを保存する必要があります。GitHub Actionsではファイルやデータをアーティファクトとして保存することが出来るので今回はこの方法を利用しました。
以下のコードは
check-coverage-developのものです。check-coverage-featureも同様の処理を追加しています。
- name: Generate usecase coverage report
run: ./gradlew koverXmlReportUsecase # Kover側でUseCaseのみを対象としてカバレッジを算出するようにしています
- name: rename coverage report
run: |
mv ${{ github.workspace }}/app/build/reports/kover/reportUsecase.xml ${{ github.workspace }}/app/build/reports/kover/reportUsecaseDevelop.xml
- name: Upload coverage reports as artifacts
uses: actions/upload-artifact@v4
with:
name: coverage-reports-develop
path: |
${{ github.workspace }}/app/build/reports/kover/reportUsecaseDevelop.xml
rename coverage report:保存するファイルがターゲットブランチのものかソースブランチのものか判断できるようにするために、リネームをします
Upload coverage reports as artifacts:actions/upload-artifactを使うことでpathで指定した対象ファイルをアーティファクトにアップロードします
実装3:カバレッジの差分を算出する
カバレッジの差分を算出する
compare-coveragejobは各ブランチのカバレッジレポートが生成されていないと算出できないため、jobを実行する条件としてcheck-coverage-develop,check-coverage-featureの両jobが正常に完了していることを条件として加えています。これはneedsを利用することで実現可能になります。
compare-coverage:
runs-on: ubuntu-latest
needs: [ check-coverage-develop, check-coverage-feature ] # 2つのジョブが終了してから実行
次に、各ブランチでアップロードしたファイルをダウンロードする必要があります。
ダウンロードには
actions/download-artifactを使います。
- name: Download coverage reports from develop
uses: actions/download-artifact@v4
with:
name: coverage-reports-develop # アーティファクト名
- name: Download coverage reports from feature
uses: actions/download-artifact@v4
with:
name: coverage-reports-feature # アーティファクト名
差分の計算はJavaScriptを作成して実行しています。各ブランチのxmlのファイルから対象クラスのカバレッジを合算します。その後、差分を算出してPRコメントに投稿します。
- 各ブランチのカバレッジ計算処理の一部(以下はdevelopブランチの場合の例を示しています)
import fs from 'fs';
import path from 'path';
import xml2js from 'xml2js';
import { Octokit } from '@octokit/rest';
// GitHubの設定を環境変数から取得
const GITHUB_REPO = process.env.GITHUB_REPOSITORY;
const GITHUB_TOKEN = process.env.GITHUB_TOKEN; // GitHubのアクセストークン
const PR_NUMBER = process.argv[2]; // コマンドライン引数からPR番号を取得
const developXmlFilePath = path.join(process.env.GITHUB_WORKSPACE, 'reportUsecaseDevelop.xml'); // アーティファクトをダウンロードしてきたファイルパス
const developXmlData = fs.readFileSync(developXmlFilePath, 'utf8'); // ダウンロードファイルの読み込み
parser.parseString(developXmlData, (err, result) => {
if (err) {
console.error(err);
return;
}
//クラス数のカウンターを処理
const counters = result.report.counter;
const counter = counters[0]
console.log(`Counter Type: ${counter.$.type} ${counter.$.missed} ${counter.$.covered}`);
if (counter.$.type === 'INSTRUCTION') {
const missed = Number(counter.$.missed);
const covered = Number(counter.$.covered);
const total = missed+covered;
developCoverage = (covered / total) * 100; // カバレッジを算出
// カバレッジデータを報告
coverageReport += `develop カバレッジ: ${developCoverage.toFixed(2)}% \n`;
};
});
// featureブランチも同様に計算する
if (developCoverage != 0 && featureCoverage != 0) {
coverageReport += `${(featureCoverage-developCoverage).toFixed(2)}% \n`; // 両カバレッジが計算できたら差分を算出する。
};
// PRにコメントを追加
const commentBody = coverageReport || 'INSTRUCTIONのカバレッジ情報はありません。';
// GitHub PRコメントの投稿
octokit.issues.createComment({
owner: GITHUB_OWNER,
repo: GITHUB_REPO_NAME,
issue_number: PR_NUMBER,
body: commentBody,
}).then(() => {
console.log('Comment posted to PR successfully!');
}).catch(error => {
console.error('Error posting comment:', error);
});
結果
上記を実装してPR時にGitHub Actionsが実行されると以下のようにカバレッジの差分が算出されます。

今回の実装により、実際にUT作成対象のクラスが実装されたPRにてUTの作成漏れによってカバレッジが下がっている事を検知することができました。レビュー時点で指摘してUT作成まで行うことができました。結果として意識付けすることができたと思います。
まとめ
今回はGitHub Actionsを使ってカバレッジをブランチ毎に計測して差分算出できるようにしました。その結果、PR時点で指摘漏れすることのあるUTの作成漏れを検知することができ、UT作成への意識付けを強くすることができました。 今後の展望としては、以下のことが出来るとよりUTへの意識付けが出来るようになると思います。
- PR対象のクラス毎のカバレッジ差分も出せるようする
- カバレッジが下がった場合はマージブロックをかけるようにする
杉山裕哉