🕌

Codecovは難しい、だけど役に立つ

2024/05/09に公開

こんにちは、バックエンドエンジニアの永田です。飼い犬が換毛期のため、最近は毎日のブラッシングが日課兼趣味になっております。

本日は、Codecov というコードカバレッジの測定・可視化・向上のためのツールを導入したお話です。

Codecovとは

そもそもコードカバレッジとは、自動テストの実行中にソースコードのどの部分が実行されているか、ソースコード全体の何%が自動テストによってカバーされているかのことです。

大抵の言語やそのテストフレームワークは、テスト実行に際しコードカバレッジに関するレポートを生成する機能を備えています。

Codecov にそのカバレッジレポートをアップロードすることで、カバレッジの変化のトレンドを見ることができたり、カバーされている部分・されていない部分を可視化できたり、Github Actions のワークフローで設定したカバレッジ目標(%)の達成を強制することができたりします。

カバレッジの変化のトレンドが見ることができる

codecov_console

カバーされている部分・されていない部分を可視化できる

uncovered_warning

PR_comment

ワークフローでカバレッジ目標を強制できる

workflows

なぜ導入したのか

自動テストの少なさがリファクタリングのハードルを高くしていると感じていたからです。Codecov によってテストを書くモチベーションを高めたり、テストがより書かれるようになる仕組みを構築できればなと思っていました。

悩みの始まり

三ヶ月ほど前に無料トライアルをはじめ、導入してみました。(トライアル終了後も無料枠の範囲で利用することができ、現在もまだ無料枠での利用です。)

しかし導入当初から現在まで感じていることとして、Codecove は難しいです。 一筋縄ではいきません。

どう難しいのか、難しさを踏まえてどのような運用にしているのかといった点にフォーカスして共有できればなと思います。(具体的な設定方法には言及しません。そちらは公式ドキュメント等に譲ります。)

導入してみたが、めちゃめちゃワークフローが Fail する

エラーではなく、デフォルトの設定のカバレッジ目標が達成できずに Github Actions のワークフローが Fail するということです。そして、必ずしも自動テストを全然書いていないからではないのです。

前提として Codecov には、project カバレッジpatch カバレッジという二つの指標が存在します。前者はプロジェクト全体のカバレッジで、後者は PR の変更差分におけるカバレッジです。

両者とも PR 作成時に達成しなければならないカバレッジ目標(%)を設定することができ、Github Actions のワークフローでチェックすることができます。

デフォルトの設定では、それぞれのカバレッジ目標は現在の(PR ブランチのベースコミット時点での)project カバレッジの数値になり、両方ともワークフローでチェックされます。

※コードカバレッジの数値の具体的な算出方法自体は、各種テストフレームワークやその設定によります。

project カバレッジのチェックで Fail する

project カバレッジを悪化させる PR を、マージしてはいけないのは当たり前?
しかしそう単純ではないのです。

例えば半年前にある機能をリリースしたとします。素晴らしいことにその機能のコードには自動テストが網羅されており、他の箇所に比べてカバレッジが著しく高かったです。しかし残念なことに何らかの理由で機能を削除することになり、そのための PR を作成しました。当然、project カバレッジは悪化するのでワークフローは Fail します。デッドコードの削除等でも同じことは起きえます。

patch カバレッジのチェックで Fail する

自分が編集した差分は、責任を持って一定のカバレッジ目標を達成すべき?
それはそうなのですが、フェアではない Fail もあります。

例えば自動テストが全く存在しない古い既存のクラスがあったとします。さらにそのクラスにはいくつかのタイポも含まれていました。せめてタイポだけでも治そうと、ある PR でタイポを修正したとします。その場合も patch カバレッジは 0%になってしまいます。また、同じ PR で新しいコードを書いていてそちらには自動テストを完備していたとしても、patch カバレッジは合算されて低く出てしまいます。

あるいは例えば、現在の project カバレッジが 60%だったとします。そしてある PR で、カバレッジが 10%に満たないコードに対して自動テストを頑張って書き、カバレッジを 50%まで上げました!でも残念、60%に満たないのでワークフローは Fail です。

仕方のない Fail は無視すればいい?

そのような考え方もあるかもしれません。しかし今回の PR での Fail が、仕方のないものだったのか改善の必要性を示唆しているのかを毎回判断するのは面倒ですし、その両方である場合もあります。Codecov のワークフローや PR コメントを毎回無視するようになってしまったら元も子もありません。

プロジェクトによっては全ワークフローの Pass をマージの要件にしている場合もあります。

ではどのような運用にする?

そもそもコードカバレッジは何%を目指すべきなのでしょうか、100%が理想?
そうではありません。例えば単純な Getter や Setter に単体テストを書いても意味がありませんし、データーベースへの操作を含むロジックについて毎回接続エラーの経路をテストするのは有益とは思えません。

正解はないのですが、Google Testing Blog というサイトのCode Coverage Best Practices という記事を参考に、60%が acceptable であり、一旦そこを目指すということにしました。

そしてこの 60%目標をどのように適用すべきか。唐突ですが、CastingONE では設計パターンとして DDD を採用しています。Domain レイヤーのコードが最も重要であり、テストの重要性も高いです。

そこで、Codecov のComponentsという機能を用いて Domain レイヤーに属するコードを抽出し、Domain レイヤーへの変更差分に限定して、patch カバレッジ 60%以上という目標を設定することにしました。 これは我ながら良い方針だったと思っています。(一旦それ以外のワークフローでのチェックは止めています。)

  • Domain レイヤーのコードの新規作成やリファクタリングの際に、十分なテストを書かなければならなくなる。
  • ワークフローでチェックされる箇所が限定され、前述のようなフェアではない Fail によるノイズが減る。Domain レイヤーでもそれは起きうるが、カバレッジが 60%に満たない Domain レイヤーのコードが放置されているのは危険なので、それを機にテスト実装やリファクタリングをすべき。

カバレッジの数値目標や、ワークフローをパスさせることに納得感が持てるようになり、テストを書くモチベーションを高めることができるようになったと思います。

domain_check

その他の細かな工夫

以下は細かな話なので、より興味のある方向けです。
  • モノレポ内の各種アプリのカバレッジレポートにFlagをつけてアップロードすることで、アプリごとのカバレッジのトレンドを測定できるようにした。
  • PR コメントに表示される内容を必要最低限のものに絞った。
    • デフォルトの設定だと情報量が多すぎて圧倒されてしまうため。
  • Codecov は PR の Files changed 上で、カバーされていない行に対してインラインコメントで警告を出すのだが、インラインコメントをオフにしてCodecov の Chrome 拡張を使って警告を見る運用にした。
    • コードビューの際のノイズになるため。Chrome 拡張で見られる警告は、表示非表示をワンボタンで切り替えられる。
    • 特に Go 言語のコードではif err != nil { return err }のようなコードが登場することが多く、その経路をカバーする価値がない場合もままあるため。

今後の展望

冒頭に述べたように、現在はまだ Codecov の無料版での利用となっています。効果的な運用についてわかってきたので、これからOSS 版を導入してフルに活用していきたいと思っています。誰もが当たり前に十分な量の自動テストを書き、安心してリファクタリングを行える環境を目指します!

(無料版だと組織内の 5 人までしか Codecov のコンソールにアクセスすることができず、その 5 人の PR にしかコメントが付きません...。カバレッジレポートのアップロード自体は、組織内の誰の PR のワークフローでも行うことができます。)

おわりに

この記事が Codecov の導入を考えている方や、効果的な運用を考えている方の参考になれば幸いです。

また CastingONE では、エンジニアの採用を積極的に行っています!開発体験の向上に興味がある方大歓迎です。コンタクトをお待ちしております!

https://www.wantedly.com/companies/company_8350157/projects

Discussion