この記事はフラー株式会社 Advent Calendar 2023の1日目の記事です。
カバレッジ計測ツール octocov について
ここ2〜3ヶ月ほど前から、弊社サーバーサイドで k1LoW/octocov の導入が少しブームになっています。 octocov はGoプロジェクトで出力したテストのカバレッジを集計・レポートしてくれるツールで、GitHub Actionsに組み込むと簡単にコードカバレッジを可視化することができます。
octocov の良いところは、CodecovやCoverallsといった既存のカバレッジ計測サービスとは異なり、アカウント作成やGitHubリポジトリの外部連携が不要で導入ハードルが低い点です。 弊社はクライアントワークをやっている都合上、利用する外部サービスの選定やアカウント管理、ソースコード・情報の取り扱いに特に配慮しなくてはいけません。 octocov はすべてGitHubリポジトリ内で完結するため、そういった難しいことを考えずにサッと導入できるのが強みですね。
octocov に関するTips
octocov の基本的な使い方については、GitHubリポジトリのREADMEや世のエンジニアのブログにたくさん情報が載っているので、ここでは触れません。 今回はcodecovを少し特殊な要件で利用したいときのTipsを紹介します。
go.mod がリポジトリルートに配置されていない場合
多くのGoプロジェクトでは、特別な理由でもない限り go.mod をリポジトリのルートに配置すると思います。しかしのっぴきならない事情により、 go.mod をルートに置かない(置けない)ケースもあるかと思います。
具体例を挙げると、ISUCONのリポジトリとか。isucon/isucon12-qualifyリポジトリでは、色んな言語の実装を並列で載せている都合上、 webapp/go/
以下にGoプロジェクトが存在します。
GitHub Actionsでこのようなリポジトリに対してテストを実施する場合、ワークフローのステップ中でディレクトリを移動しつつテストを実行するような書き方をすると思います。
- name: test run: | cd webapp/go/ go test ./... -coverprofile=coverage.out
素直にこのように実装した場合、カバレッジのプロファイルは webapp/go/coverage.out
として出力されます。
octocov-action には実行ディレクトリを指定するようなオプションがなく、常にルートディレクトリで octocov を実行するようです。この場合、どのようにプロファイルを読み込ませれば良いでしょう?
A. 設定ファイルの中でプロファイルのパスを指定する
octocov の設定ファイルである .octocov.yml
に、集計対象のプロファイルのパスを渡してあげます。例えば次のような形です。
coverage: paths: - webapp/go/coverage.out
B. Goプロジェクトに設定ファイルを作成し、そのパスを指定する
octocov の設定ファイルをGoプロジェクトのパスに作成( webapp/go/.octocov.yml
)します。
octocov には設定ファイルを指定する --config
オプションがあるため、これで設定ファイルの場所を指定してあげれば良いです。
octocov --config webapp/go/.octocov.yml
どうやらデフォルトで設定ファイルと同じディレクトリにあるカバレッジプロファイルを読むような実装になっているようです。
GitHub Actionsで同様のことを実現したい場合は、以下のように config
パラメータを指定してあげればOKです。
- name: test run: | cd webapp/go/ go test ./... -coverprofile=coverage.out - uses: k1LoW/octocov-action@v0 with: config: webapp/go/.octocov.yml
マルチモジュールで動かす場合
octocov の GitHub Actionsはとても便利で、レポート作成時にmainブランチなど特定時点のカバレッジとのDiffをとり、その差分も含めてレポートしてくれます。 なので、例えばテストをサボってゴリゴリと新機能を追加したりすると、mainブランチからカバレッジが低下していく様子が一発で見えてしまうわけです。便利ですね。
さて、この便利な機能ですが、マルチモジュールで使う分には少しだけ注意が必要です。
例えば以下のように、 module1
module2
の2モジュールを抱えるプロジェクトがあり、各モジュール毎にテストカバレッジを収集しているとします。
├── module1 │ ├── .octocov.yml │ ├── go.mod │ └── go.sum └── module2 ├── .octocov.yml ├── go.mod └── go.sum
一般論はわかりませんが、このような構成の場合、 module1 のテストと module2 のテストを別々のGitHub Actionsワークフローとして定義し、 変更が入ったモジュールだけテストが実行されるような設定をするんじゃないでしょうか(少なくとも、弊社ではそうしています)。
ある時点において、 module1とmodule2のカバレッジがそれぞれ 50%、70% だったとします。 そこから module1 のテストを頑張って書いてカバレッジを 60% にするPull Requestを作成したとき、その直前の状態によって「50%から増えた!」とレポートされる場合と「70%から減った!」とレポートされる場合があります。 つまり module1 のカバレッジレポートのDiffととる際、module2 のカバレッジレポートが参照されてしまう可能性があるのです。
カバレッジのDiffがうまくいかない原因
octocovがレポートのDiffをとる設定は、 octocov init
で設定ファイルを作成した場合、次のようになっているはずです。
diff: datastores: - artifact://${GITHUB_REPOSITORY} report: if: is_default_branch datastores: - artifact://${GITHUB_REPOSITORY}
デフォルトブランチでワークフローが回ったときにレポートを artifact://${GITHUB_REPOSITORY}
に保存し、そのレポートを元にDiffをとる、という設定です。
artifact://
スキームを設定すると、GitHub Actions の Workflow artifacts を使用します。レポートファイルがどのように保存されるかは、octocovのREADMEを参照してみましょう。
https://github.com/k1LoW/octocov#github-actions-artifacts
Use
artifact://
orartifacts://
scheme.artifact://[owner]/[repo]/[artifactName]
artifact://[owner]/[repo]/[artifactName]
artifact://[owner]/[repo]
( default artifactName:octocov-report
)
……はい、バッチリ書いてありますね。
そうです、octocov はデフォルトで octocov-report
という名前でレポートを保存します。
つまり複数モジュールのレポートが全部この名前で保存されてしまうため、最後にどのモジュールのテストが走ったかによって、その後のレポート表示が変わってしまうという状態になります。
これは思いっきり業務の中でハマりまして、マルチモジュールのプロダクトにoctocovを導入したら、タイミングによってカバレッジが急上昇したり急低下する現象に見舞われました。
保存されるレポートをモジュール毎に分ける
各モジュールの設定ファイルにおいて、次のように設定を変更しましょう。
diff: datastores: + - artifact://${GITHUB_REPOSITORY}/octocov-report-module1 - - artifact://${GITHUB_REPOSITORY} report: if: is_default_branch datastores: + - artifact://${GITHUB_REPOSITORY}/octocov-report-module1 - - artifact://${GITHUB_REPOSITORY}
module1
の部分はモジュール毎に衝突しない(つまり、設定ファイル間で重複しない)一意の名称をつけます。まぁ普通にモジュール名で良いでしょう。
まとめ
octocov のちょっとニッチな設定方法を紹介しました。 とても手軽に使えて便利なツールなので、これからもどんどん広がっていくことを願います。
次回は @chooblarin さんで「なにか書く」です。お楽しみに。