ソフトウェアエンジニアの福井です。
コードカバレッジのパーセンテージを上げる(または保つ)ことを強制することは悪いプラクティスとされます。
そのためRettyではいくつかのプロジェクトで、パーセンテージによってmergeできないなど強制せず、カバレッジのパーセンテージのみを見える化していました。
しかしどのコードがカバーされてるか参照できるカバレッジレポートが身近にありませんでした。
どのコードがカバーされてるかを参照することで以下のメリットがあります。
2つ目の"コード設計面での気づきが得られる"についての説明です。
飲食店の予算をチェックする関数(言語はGo)があるとします。
予算が¥1,000~¥50,000の範囲で¥1,000単位であることをチェックします。
(*説明のために仕様を単純化していて、コードも1行にまとめたりしていません)
package budget func CheckBudget(b uint32) bool { if !checkRange(b) { return false } if !checkUnit(b) { return false } return true } func checkRange(b uint32) bool { return 1000 <= b && b <= 50000 } func checkUnit(b uint32) bool { // A if b < 1000 { // B return false } return b%1000 == 0 }
Aでは入力が0だとtrueで判定してしまうためのチェックです。
これに対してテストコードを書きます。
package budget_test import ( "coverage/budget" "testing" ) func TestCheckBudget(t *testing.T) { tests := []struct { budget uint32 want bool }{ {budget: 1000, want: true}, {budget: 50000, want: true}, {budget: 49999, want: false}, {budget: 0, want: false}, // C } for _, tc := range tests { if budget.CheckBudget(tc.budget) != tc.want { t.Error("unexpected result") } } }
Cのbudgetが0のテストケースを実行してもBの行はカバレッジが通りません。
なぜなら前のcheckRange関数で0を弾いているからです。
この場合checkRangeとcheckUnit関数の責務を重複させないために、1つの関数にまとめるなど考えられるかもしれません。
このように関数の責務や粒度のようなコード設計に気づかされます。
Google Testing Blogでも"コードがカバーされてないことに意味がある"とあります。
GoのコードカバレッジレポートをPull Requestにコメントする
このようなメリットのあるコードカバレッジレポートをGoのプロジェクト開発で簡単に参照したく、Codecovなどのサービスを使わずに低予算でカバレッジレポートをPull Requestコメントし参照できるようにしました。
なおCIはCircleCIを使っており、Go標準のカバレッジレポートを使用します。
流れ
- GitHubでPull Request open
- カバレッジレポートを出力しCircleCI Artifactにアップロード
CircleCI Artifactはジョブが完了した後もデータが保持され、ビルドプロセス出力を格納するストレージとして使用できます - アップロードしたURLをPull Requestにコメント
実際のCircleCIコード
既存のCircleCIコードにworkflowを追加します。
jobs: # 他のjob post-coverage-report: executor: name: golang steps: - checkout - run: command: | go test -coverprofile=cover.out -coverpkg=./... ./... go tool cover -html=cover.out -o cover.html - store_artifacts: path: cover.html destination: coverage-report - run: name: Post Artifact URL to GitHub PR command: | GITHUB_COMMENT_VERSION=6.0.3 if [ -z "${CIRCLE_PULL_REQUEST}" ]; then echo "This step is not by a pull request. Skip posting coverage report." exit fi apt update && apt install -y jq ARTIFACT=$(curl -X GET "https://circleci.com/api/v2/project/github/${CIRCLE_PROJECT_USERNAME}/${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BUILD_NUM}/artifacts" \ -H "Accept: application/json" --header "Circle-Token: ${CIRCLE_TOKEN}" | jq -r '.items[0].url') PR_NUMBER=$(basename ${CIRCLE_PULL_REQUEST}) COMMENT_BODY="Coverage report is available [here](${ARTIFACT})." echo $COMMENT_BODY curl -L https://github.com/suzuki-shunsuke/github-comment/releases/download/v${GITHUB_COMMENT_VERSION}/github-comment_${GITHUB_COMMENT_VERSION}_linux_amd64.tar.gz -o linux_amd64.tar.gz tar -zxvf linux_amd64.tar.gz ./github-comment hide --org ${CIRCLE_PROJECT_USERNAME} --repo ${CIRCLE_PROJECT_REPONAME} --pr ${PR_NUMBER} -condition 'Comment.HasMeta && Comment.Meta.TemplateKey == "default"' ./github-comment post --org ${CIRCLE_PROJECT_USERNAME} --repo ${CIRCLE_PROJECT_REPONAME} --pr ${PR_NUMBER} --template "${COMMENT_BODY}" workflows: version: 2 # 他のworkflow post-coverage-report-workflow: jobs: - post-coverage-report: filters: branches: ignore: main
またCIrcleCI Environment Variablesに以下を設定します。
- GITHUB_TOKEN: Pull RequestにWriteできるGitHub Personal access tokens
- CIRCLE_TOKEN: CircleCI Personal API Token
Only build pull requestsは有効化してる前提です。
動作
- Pull Request openするとコメントされます。
- URLを選択するとGo標準カバレッジレポートを参照できます。
- コミットを積むと新しくコメントされ、前のコメントがhideされます。
解説
Pull Requestコメントにgithub-commentというCLIを使用してます。 2024年3月時点ではGitHub APIでPull RequestコメントをhideするAPIはGraphQLでしか提供してないため、このCLIを使うのが便利です。
アップロードしたArtifact URLを取得するCircleCI API v2は現在Personal API Tokenのみをサポートしてます。 Personal API TokenはProject API Tokenのような権限設定はできないため管理に注意が必要です。
CircleCIとCircleCI Artifactの代わりにGitHub ActionsとGitHub Actions Artifactを使うことも考えられます。 ですが2024年3月時点でGtiHub Actions ArtifactはCircleCI ArtifactのようにHTMLファイルをブラウザで開けないので、対応が待たれます (issue)。
post-coverage-report-workflow は他workflowとは別に作成しています。 post-coverage-report-workflowをGitHubのBranch protections status checkから外すなどすれば、もしCI設定ミスでpost-coverage-report-workflowが失敗してもmergeできる設定にしやすいためです。
まとめ
(コミット積むたびにPull Requestにコメントしてしまいますが、)どのコードがカバーされてるか参照できるカバレッジレポートが身近になりました。コード設計面で気づきを得る機会が増えると思います。
以上です。