はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#, Actions】coverlet.collector + octcovを用いてプルリクを出した際にコードカバレッジの増減をCIで表示する

はじめに

今回はC#でプロジェクトを構築している前提でPRを出した際にコードカバレッジの増減を表示してくれるCIの作り方を紹介したいと思います。

コードカバレッジの増減表示

具体的にはcoverlet.collectoroctcovというOSSを利用して実現していきます。

github.com
github.com

coverlet.collectorを用いたテスト実行 + コードカバレッジ取得

概要

coverlet.collectorとは.NET用のコードカバレッジフレームワークです。これを用いることでdotnet testを実行した際に、一緒にコードカバレッジを取得することができます。

Coverlet is a cross platform code coverage framework for .NET, with support for line, branch and method coverage. It works with .NET Framework on Windows and .NET Core on all supported platforms.

// DeepL翻訳
Coverletは.NET用のクロスプラットフォームのコードカバレッジフレームワークで、行、ブランチ、メソッドのカバレッジをサポートしています。

また出力フォーマットには以下に対応しています。

  • cobertura
  • json
  • lcov
  • opencover
  • teamcity

プロジェクトに導入する

XUnitNUnitにも対応しています。テストプロジェクトに対してNuGet経由でインストールを行なってください。

$ dotnet add package coverlet.collector
RiderでNuGetからインストールしている様子

コマンド利用方法

dotnet testを実行する際に、--collect:"XPlat Code Coverage"を追加してあげます。

# 必須 : --collect:"XPlat Code Coverage" 
# 任意 : Formatにより出力フォーマットを設定できる。cobertura、json、lcov、opencover、teamcityに対応
# 任意: --results-directoryにより出力先フォルダを指定できる。出力先ディレクトリ/<GUID>/coverage.cobertura.xmlのように出力され、GUIDは必ずついてくる
$ dotnet test --collect:"XPlat Code Coverage;Format=cobertura,lcov;"  --results-directory:"./Result"
 Determining projects to restore...
  復元対象のすべてのプロジェクトは最新です。
...
成功!   -失敗:     0、合格:     1、スキップ:     0、合計:     1、期間: 6 ms - TestProject.dll (net8.0)

添付ファイル:
  <ProjectRoot>/Result/4d630dc9-7e14-4956-b55c-d4acce16eeae/coverage.cobertura.xml
  <ProjectRoot>/Result/4d630dc9-7e14-4956-b55c-d4acce16eeae/coverage.info

またコメントでも書きましたが、出力されるファイルは必ずGUID名のディレクトに中に格納されます。どうやらこのディレクトリをなくすことはできないようです。

取得範囲を絞る

--colectにオプションを増やすことで、カバレッジを取得するパスやアセンブリを指定することができます。

ExcludeIncludeIncludeDirectoryIncludeTestAssemblyExcludeByAttributeなどなど多様なオプションがあるので公式ドキュメントを参照してみてください。
coverlet/Documentation/VSTestIntegration.md at master · coverlet-coverage/coverlet · GitHub

octcovを用いてプルリクの際にコードカバレッジ差分を表示する

概要

octocovコードメトリクスを収集するためのツールキットです。CLIとしても利用できますし、CIでも利用できます。

octocov is a toolkit for collecting code metrics (code coverage, code to test ratio, test execution time and your own custom metrics).

// DeepL翻訳
octocovは、コード・メトリクス(コード・カバレッジ、コード対テスト比、テスト実行時間、独自のカスタム・メトリクス)を収集するためのツールキットです。

またカバレッジレポートのフォーマットは沢山あるのですが、octcovは以下に対応しています。

  • Go coverage
  • LCOV
  • SimpleCov
  • Clover
  • Cobertura
  • JaCoCo

coverlet.collectoroctcovは両方ともLCOVに対応しているので、今回はそれを利用していきます。

設定ファイルの作成

octocovには設定ファイルを利用することで様々な設定ができます。場所はどこでも良いのですが.octocov.ymlファイルを作成して中身を記述してください。

詳細はreadmeを見てみて欲しいのですが、私がよく利用する設定を載せておきます。
github.com

# .octocov.yml

# コードカバレッジが60%以上でないとexit 1を返す
# coverage/*/coverage.infoを読み込む(coverlet.collectorでLCOVの出力先をここに設定する)
coverage:
  acceptable: 60%
  paths:
    - coverage/*/coverage.info
      
# テストの実行時間として利用するsteps (Actions側でnameを'dotnet test'にしている場合)
testExecutionTime:
  steps:
    - 'dotnet test'

# codeとtestの割合を計算するのに利用するファイル
codeToTestRatio:
  code:
    - 'ClassLibrary1/**/*.cs'
  test:
    - 'TestProject/**/*.cs'
      
# プルリクエストにコメントをつける
comment:
  if: is_pull_request
  hideFooterLink: true

# JobのSummaryを表示する
summary:
  if: true
  
# 差分を表示するためにArtifactを利用
report:
  if: is_default_branch
  datastores:
    - artifact://${GITHUB_REPOSITORY}/dotnet-test-report
      
diff:
  datastores:
    - artifact://${GITHUB_REPOSITORY}/dotnet-test-report

actionsを作成する

最後にcoverlet.collector + octcovを利用するActionsを書きます。

name: test
on:
  push:
    branches: main
  pull_request:
    branches: main

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write
      actions: read
    env:
      LCOV_DIR: ${{ github.workspace }}/coverage
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 8.0.x
      - run: dotnet restore
      - run: dotnet build --configuration Release --no-restore
      - name: dotnet test
        run: dotnet test --configuration Release --no-restore --no-build --verbosity normal --collect:"XPlat Code Coverage;Format=lcov;"  --results-directory:${{ env.LCOV_DIR }}
      - uses: k1LoW/octocov-action@v1
        with:
          config: .octocov.yml
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPOSITORY: ${{ github.repository }}

結果

プルリクエストを出すとコードカバレッジの増減を表示してくれ、デフォルトブランチにマージするとArtifactに結果を保存してくれます。

また以下の3つを設定している + デフォルトブランチとの差分表示をするように設定しています。

  • Code Coverage : コードカバレッジ
  • Code to Test Ratio : コードとテストコードの比率
  • Test Execution Time : テスト実行時間
プルリクを作成するとコードカバレッジの増減を表示してくれる

また設定したcoverage: acceptable 60%以下の場合はfailになります。

コードカバレッジが低い場合

考察

PR単位でコードカバレッジ増減を表示することで、PR作成 => カバレッジ低下 => テストコード作成の流れができて、良い運用ができるのではないかなと思います。

定期的にカバレッジを取得するのも良いかとは思うのですが、実装した際に一緒にテストコードを実装するのが個人的には理想かなと。

さいごに

またUnityで公式パッケージであるCode Coverageを利用すると同様のことができるので、それも後でブログにて紹介しようと思います。