2024年版 コードカバレッジ可視化の「ちょうどいい」やり方


こんにちは。estie VPoEの青木啓剛です。

突然ですがみなさん、コードカバレッジ計測していますか?

ソフトウェア開発においてテストの重要性は語るまでもないですが、そのテストがどの程度の網羅性をもっているかについて計測したり可視化したりするための仕組みについて最近見直しを行ったため、その試行錯誤を紹介したいと思います。

コードカバレッジ計測の意義

本題に入る前に、コードカバレッジの計測は果たしてどのような意味があるのか、という点を抑えておく必要があります。

コードカバレッジに関して一般的によく言われるのは、一定のカバレッジがあることがソフトウェアの品質を高く保つために必要だという主張です。実はこの点については諸説あります。著名なソフトウェア開発者Martin Fowler氏のブログ記事『Test Coverage翻訳記事)』をはじめ、100%は不要ながら一定程度のカバレッジは品質にプラスの影響がある、という主張も多い一方で、2018年にVard Antinyanらによって書かれた論文『Mythical Unit Test Coverage』ではコードカバレッジと欠陥のないソフトウェアをつくることとの相関はない、と言い切られています。こういった背景も踏まえて、私としては具体的なカバレッジ目標の設定については重要ではないと考えるようになりました。

ただし一方で、以下の点においてカバレッジを見える化することの意味は大きいとも考えています。

  • Pull Requestに対してカバレッジの増減がコメントされることで、テストを書こうという意識づけのきっかけになる。
  • プロダクトの状況によっては、あるいは特定のコンポーネントに対しては、テストカバレッジを意識的に向上させたいという場合が発生し得る。その際にテストが通過してない行を特定できるのは嬉しい。

今回の見直し以前はどうやっていたか

コードカバレッジ計測のツールとして広く利用されているCodecovを契約し、すべてではないですが複数のGitHubレポジトリのCIでテストを実行し、カバレッジレポートを送信していました。

前提として、estieにおいては以下のようなレポジトリ構造をとることが多いです。

/
 |
 +-- backend/  ※バックエンドのコード(RustだったりTypeScriptだったり言語はバラバラ)
 |
 +-- frontend/  ※フロントエンドのコード(TypeScript/React)
 |
 +-- playwright/  ※E2Eテストのコード(TypeScript/Playwright)
 |
 +-- README.md など

複数のプロダクトを並行で開発していますが、ソフトウェアエンジニアはプロダクトに紐づく形でアサインされ、フロントエンドとバックエンドの両方のコードにコミットすることが多いです。そのため、プロダクト単位でレポジトリを分割して、その中に言語ごとのディレクトリを配置しています。

このような構造でカバレッジを計測する場合、フロントエンドとバックエンドでテストツールが違うためコードカバレッジもそれぞれで算出することになります。CodecovにはFlagsという機能があり、同一レポジトリであっても複数のカバレッジレポートを管理することができるために使い勝手としては申し分ありません。

複数のカバレッジレポートを計測している例

一方で、Codecovは開発者人数あたりのライセンス料金設定となっており、組織規模拡大と比例してコストが増大しつづけることや、開発メンバーの入れ替わりに応じたライセンスのつけ外しの工数については可能ならば改善したいと考えていました。結果として、テストカバレッジ計測に積極的な開発チームのメンバーにのみライセンスが付与され、開発部門全体での利用促進しづらい状況となってしまっていたのが実情です。

ライセンス数を気にせずカバレッジを意識できる環境へ

まずはじめに検討したのはCodecovを社内のインフラで運用することです。Codecovは有料のSaaSでありながら、自社で管理するクラウドサービスにセットアップすることでライセンス上限なく無償で利用することが可能です。しかしながら、実際に環境構築を試したところ、すべての機能を利用するためには時系列データベースであるTimescaleDBを含めていくつかのデータストアを運用する必要がありました。私たちの会社規模だと保守運用にかかる工数が見合わないと判断し、この選択肢は見送りました。

他にもいくつかの選択肢を検討した結果、GitHub Actionsでコードカバレッジを集計・レポートしてくれるoctocovを中心とした仕組みを構築し、Codecovからの切り替えを進めようとしています。Codecovを利用することに比べれば機能は限定されますが、実用上必要十分な環境が構築できました。

octocov + GitHub Pagesでのコードカバレッジ可視化

octocovを動かすために、以下のようなワークフローでGitHub Actionsを実行します。


フロントエンド・バックエンドのそれぞれでoctocovが対応した形式でカバレッジレポートを出力し、それを統合してメトリックスを計測することで、レポジトリ全体の変化を追うことが可能です。対応フォーマットの中でもlcov(GCCのカバレッジツールであるgcovの拡張であり、HTML形式でカバレッジを表示するためのツール)は多くのテストツールでサポートされており、開発言語を問わず利用しやすいです。

Pull Requestにoctocovがコメントしてくれる

具体的には以下のようなワークフローを定義します。バックエンドをRust、フロントエンドをTypeScript(テストツールはvitest)で実装するプロジェクトを想定したGitHub Actionsのサンプルです。

name: Report Code Coverage

on:
  push:
    branches:
      - main
  pull_request:

permissions:
  pull-requests: write

jobs:
  backend:
    name: Measure Backend Coverage
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./backend
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: taiki-e/install-action@cargo-llvm-cov
      - name: Generate code coverage
        run: |
          mkdir -p target/llvm-cov
          cargo llvm-cov --all-features --workspace --lcov --output-path target/llvm-cov/lcov.info
      - uses: actions/upload-artifact@v4
        with:
          name: lcov_backend
          path: ./backend/target/llvm-cov/

  frontend:
    name: Measure Frontend Coverage
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ./frontend
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Install dependencies
        run: npm ci
      - name: Generate code coverage
        run: npm run test -- run --coverage
      - uses: actions/upload-artifact@v4
        with:
          name: lcov_frontend
          path: ./frontend/coverage/

  report:
    name: Collect Metrics
    needs:
      - backend
      - frontend
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          path: ./coverage
          pattern: lcov_*
      - uses: k1LoW/octocov-action@v1

ただし、これだと冒頭のセクションで述べたポイント2つ「Pull Requestに対してカバレッジの増減がコメントされること」「テストが通過してない行を特定できること」のうち、後者についてはローカル環境でカバレッジレポートをHTML形式で出力し、そのファイルを参照する必要があります。日々の開発における面倒を減らすため、これもGitHubレポジトリ内で解決させたいです。

そこで、最後のjobを以下のように修正してGitHub Pagesでカバレッジレポートを参照可能とすることにしました。(実際には各言語のカバレッジ出力部分にも一部修正を入れています。)


    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          path: ./coverage
          pattern: lcov_*
      - uses: k1LoW/octocov-action@v1
      - name: Setup pages
        run: |
          mkdir -p coverage/html
          mv coverage/lcov_backend/html coverage/html/backend
          mv coverage/lcov_frontend/lcov-report coverage/html/frontend
          echo "<ul><li><a href="backend">backend</li><li><a href="frontend">frontend</li></ul>" > coverage/html/index.html
      - uses: rajyan/preview-pages@v1
        with:
          source-dir: coverage/html
          branch-per-commit: false
          pr-comment: hide_and_recreate

コミットの都度、独立したURLでカバレッジレポートを参照可能とするために Preview PagesというActionを利用しました。(ちなみに余談ですが、このActionは世界的にも多く利用されている Deploy to GitHub Pagesをベースにして弊社エンジニアの木村が改良したものです!)

フロントエンドとバックエンドでそれぞれ別のHTMLが出力されてしまうので、その目次となるインデックスページだけ追加すれば、Pull Requestに以下のような形でコメントが自動投稿され、URLの先には各言語のカバレッジレポートが存在する状況をつくることができました。


記事公開時点では一部のプロジェクトのみがこの新しい仕組みに切り替わった状態ですが、今後は徐々に今回紹介した形で運用を統一していけたらと考えています。

なお、今回サンプルとして構築したテストプロジェクトのソースコードは GitHub - hoyo/octocov-testにて公開していますので、似たような仕組みを構築したい場合は参考にしていただけたらと思います。

最後に

今回の記事では、開発者全員がコードカバレッジを意識しやすい環境をつくるための取り組みについて紹介しました。estieでは、事業の成長を踏まえて、今後はこれまで以上に品質を意識したプロダクトづくりを進めていきたいと思っています。エンジニアリングを活用して品質課題に向き合ってみたい!という方はぜひ一度カジュアルにお話しましょう!

hrmos.co

© 2019- estie, inc.