やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

.NET でコードカバレッジを収集&レポートする

.NET6 を使ったアプリの開発中にコードカバレッジの収集とレポートを出力しようとしたのですが、地味に情報がまとまっていなかったので残しておこうと思います。 なお、コードカバレッジにはcoverlet.collector、レポート出力にはReportGeneratorを使用します。 coverlet.collectorxUnit.NETが規定で統合しているため、選択肢として挙がりやすいと思います。

開発環境

  • Visual Studio 2022
  • .NET 6.0.300
  • coverlet.collector 3.1.2
  • ReportGenerator 5.1.9

使用するソリューション

今回使用するソリューションはシンプルにコンソールアプリとしています。

Coverlet.Sampleプロジェクトに実装コードが含まれていて、Coverlet.Sample.Testsにテストコードを実装するような構成です。 テストするのは次のようなCalculatorクラスとします。

public class Calculator
{
    public int Add(int x, int y) => x + y;

    public int Subtract(int x, int y) => x - y;
}

ReportGenerator をインストールする

基本的に以下の Microsoft Docs を読めばいいんですが、地味に間違っているのがReportGeneratorのインストールコマンドです。

docs.microsoft.com

ドキュメントには次のコマンドが記載されていますが、私のローカル環境で実行したところ失敗しました。

dotnet tool install -g dotnet-reportgenerator-globaltool

こんなメッセージが出力されます。 PS C:\Users\user> dotnet tool install -g dotnet-reportgenerator-globaltool

C:\Users\user\AppData\Local\Temp\xwqllpoi.5zb\restore.csproj : error NU1301: ソース https://pkgs.dev.azure.com/iyemon018/
_packaging/All-Packages/nuget/v3/index.json のサービス インデックスを読み込めません。
ツール パッケージを復元できませんでした。
ツール 'dotnet-reportgenerator-globaltool' をインストールできませんでした。この失敗は次の原因で生じた可能性があります。

* プレビュー リリースをインストールしようとしており、--version オプションを使用してバージョンを指定しなかった。
* この名前のパッケージが見つかったが、.NET ツールではなかった。
* 恐らくインターネットの接続の問題で、必須の NuGet フィードにアクセスできない。
* ツールの名前の誤入力。

パッケージの名前付けの強制を含む他の理由については、https://aka.ms/failure-installing-tool にアクセスしてください

実際にはこちらのページに記載されているコマンドを実行します。

www.nuget.org

次のコマンドであれば成功すると思います。

dotnet tool install --global dotnet-reportgenerator-globaltool --version 5.1.9

テストを実行する

xUnit.NET + coverlet.collectでテストを実行するだけであれば次のコマンドを実行するだけで問題ありません。

dotnet test --collect:"XPlat Code Coverage"

ただし、出力される XML ファイルをレポートツールに食わせて出力する場合、フォーマットを指定する必要があります。 フォーマットのオプションはcoverlet.collectionのプロジェクトに記載されています。

github.com

フォーマットのデフォルトはcoberturaになっているので、今回のケースでは使用しなくてもいいのですが覚えておくと応用が効きます。 coberturaのフォーマットで実行する場合は次のコマンドを実行します。

dotnet test --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=cobertura

データ収集構成を設定ファイルで定義する

コマンドでフォーマットを指定することも可能なのですが、coverlet.collectではそれ以外にもデータ収集構成を設定することが出来ます。 このデータ収集構成は XML ファイルとして定義でき、実行時に指定することが出来ます。

チームで開発する場合は、このデータ収集構成の設定ファイルを Git に保存しておけば、どの環境でも同一のレポートを出力することが出来ますし、CI を使って出力する際にも使用できます。

データ収集構成は次のような構成になっています。具体的なそれぞれの項目の意味は、上記のcoverlet.collectionプロジェクトページを参照してください。

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="XPlat code coverage">
        <Configuration>
          <Format>json,cobertura,lcov,teamcity,opencover</Format>          
          <Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*</Exclude> <!-- [Assembly-Filter]Type-Filter -->
          <Include>[coverlet.*]*,[*]Coverlet.Core*</Include> <!-- [Assembly-Filter]Type-Filter -->
          <ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute</ExcludeByAttribute>
          <ExcludeByFile>**/dir1/class1.cs,**/dir2/*.cs,**/dir3/**/*.cs,</ExcludeByFile> <!-- Globbing filter -->
          <IncludeDirectory>../dir1/,../dir2/,</IncludeDirectory>
          <SingleHit>false</SingleHit>
          <UseSourceLink>true</UseSourceLink>
          <IncludeTestAssembly>true</IncludeTestAssembly>
          <SkipAutoProps>true</SkipAutoProps>
          <DeterministicReport>false</DeterministicReport>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=coberturaと同じ構成にする場合は次のようにすればOKです。

<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="XPlat code coverage">
        <Configuration>
          <Format>cobertura</Format>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

あとは次のコマンドを実行すれば設定ファイルを使用してテストの実行とコードカバレッジの収集を実行してくれます。

dotnet test --collect:"XPlat Code Coverage" --settings coverlet.collect.runsettings

レポートを出力する

テストとコードカバレッジ収集を実行すると、テストプロジェクトのフォルダ配下にTestResultsフォルダが作成され、その配下にcoverage.cobertura.xmlファイルが生成されます。 このファイルを使用して以下のコマンドを実行するとレポートが出力されます。

reportgenerator -reports:"Coverlet.Sample.Tests\TestResults\{guid}\coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html

レポートはこんな感じで出力されます。

スクリプトで実行できるようにする

コマンドラインで毎回実行するのは時間の無駄なのでテスト実行+コードカバレッジ収集+レポート出力をPowerShellで実行できるようにします。 今回のソリューションでは次のようにしました。

$runSettings = ".\runsettings.xml"
$resultDirectory = ".\.TestResults"
$reportsDirectory = ".\.TestReports"

if (Test-Path $resultDirectory) { Remove-Item $resultDirectory -Recurse }
if (Test-Path $reportsDirectory) { Remove-Item $reportsDirectory -Recurse }

dotnet test .\Coverlet.Sample.Tests\Coverlet.Sample.Tests.csproj --collect:"XPlat Code Coverage" --results-directory $resultDirectory --settings $runSettings

$xmlFileName = (Get-ChildItem $resultDirectory -Filter *.xml -Recurse -File)[0].FullName

reportgenerator -reports:$xmlFileName -targetdir:$reportsDirectory -reporttypes:Html

設定ファイルはrunsettings.xmlに保存しておき、コードカバレッジ.TestResultsフォルダに出力します。 coverlet.collectカバレッジ結果の出力フォルダを GUID で生成するため、固定のフォルダ名にすることが出来ません。 仕方ないのでGet-ChildItemでフルパスを取得するようにしています。


コードカバレッジの収集とレポート出力はあまりキャッチアップしないせいか気がつくと過去のプロジェクトでは使用できなくなっていたりします。 .NET の場合は LTS が GA されたタイミングで定期的に見直していくのが良さそうですね。