はなちるのマイノート

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

【C#】ExcludeFromCodeCoverageAttributeを用いてコードカバレッジ収集の対象外にする

はじめに

今回はdotnet testでコードカバレッジを収集する際にExcludeFromCodeCoverageAttributeを用いて対象外にする設定をする方法を紹介したいと思います。
learn.microsoft.com

コードカバレッジ収集について

コードカバレッジ収集はcoverlet.collectorを利用している前提で話をすすめていきます。
https://www.nuget.org/packages/coverlet.collector

$ dotnet add package coverlet.collector
# 必須 : --collect:"XPlat Code Coverage" 
# 任意: --results-directoryにより出力先フォルダを指定できる。出力先ディレクトリ/<GUID>/coverage.cobertura.xmlのように出力され、GUIDは必ずついてくる
$ dotnet test --collect:"XPlat Code Coverage"  --results-directory:"./Result"
  Determining projects to restore...
  復元対象のすべてのプロジェクトは最新です。

// ...

テスト実行を開始しています。お待ちください...
合計 1 個のテスト ファイルが指定されたパターンと一致しました。

成功!   -失敗:     0、合格:     3、スキップ:     0、合計:     3、期間: 13 ms - TestProject1.dll (net8.0)

添付ファイル:
  <ProjectRoot>/Result/bb585210-e6ba-45a6-a15f-4d86d782b69a/coverage.cobertura.xml

詳細については以下の記事で触れているので、利用方法が不明な方は参照してみてください。
www.hanachiru-blog.com

コードカバレッジの収集対象外に設定する

対象外にしたいクラスまたは構造体に対して[ExcludeFromCodeCoverage]を付与してあげます。

using System.Diagnostics.CodeAnalysis;

[ExcludeFromCodeCoverage]
public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}

クラスまたは構造体にこの属性を配置すると、そのクラスまたは構造体のすべてのメンバーがコード カバレッジ情報のコレクションから除外されます。

ExcludeFromCodeCoverageAttribute クラス (System.Diagnostics.CodeAnalysis) | Microsoft Learn

また実装をみてみると、公式ドキュメントには記述がありませんでしたがクラス・構造体以外にも以下に対応していそうでした。

  • Assembly
  • Class
  • Struct
  • Constructor
  • Method
  • Property
  • Event
namespace System.Diagnostics.CodeAnalysis
{
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event, Inherited = false, AllowMultiple = false)]
    public sealed class ExcludeFromCodeCoverageAttribute : Attribute
    {
        public ExcludeFromCodeCoverageAttribute() { }

        /// <summary>Gets or sets the justification for excluding the member from code coverage.</summary>
        public string? Justification { get; set; }
    }
}

実験

実際にclassに対して付与してみて、コードカバレッジの変化を見てみます。

$ tree
.
├── Program.cs
├── SampleHelper.cs
├── SampleConsoleNet8.csproj
using System.Diagnostics.CodeAnalysis;

[ExcludeFromCodeCoverage]
public class Program
{
    public static void Main()
    {
        Console.WriteLine("Hello World!");
    }
}
namespace SampleConsoleNet8;

public static class SampleHelper
{
    public static string GetSampleString()
    {
        return "Sample String";
    }

    public static string GetSampleString2(string text)
    {
        if (string.IsNullOrEmpty(text))
        {
            return "";
        }
        
        return $"Sample String: {text}";
    }
}

coberturaの変化

やや分かりづらいですが、Programに関する記述がまるっとなくなっています。

ExcludeFromCodeCoverageAttributeを付与しない場合
<?xml version="1.0" encoding="utf-8"?>
<coverage line-rate="0.6666" branch-rate="0.5" version="1.9" timestamp="1729422662" lines-covered="8" lines-valid="12" branches-covered="1" branches-valid="2">
  <sources>
    <source>----/SampleConsoleNet8/</source>
  </sources>
  <packages>
    <package name="SampleConsoleNet8" line-rate="0.6666" branch-rate="0.5" complexity="4">
      <classes>
        <class name="Program" filename="Program.cs" line-rate="0" branch-rate="1" complexity="1">
          <methods>
            <method name="Main" signature="()" line-rate="0" branch-rate="1" complexity="1">
              <!-- 省略 -->
          </methods>
          <lines>
            <!-- 省略 -->
          </lines>
        </class>
        <class name="SampleConsoleNet8.SampleHelper" filename="SampleHelper.cs" line-rate="0.8887999999999999" branch-rate="0.5" complexity="3">
          <methods>
            <!-- 省略 -->
            </method>
          </methods>
          <lines>
            <!-- 省略 -->
          </lines>
        </class>
      </classes>
    </package>
  </packages>
</coverage>
ExcludeFromCodeCoverageAttribute付与した場合
<?xml version="1.0" encoding="utf-8"?>
<coverage line-rate="0.8887999999999999" branch-rate="0.5" version="1.9" timestamp="1729422682" lines-covered="8" lines-valid="9" branches-covered="1" branches-valid="2">
  <sources>
    <source>/</source>
  </sources>
  <packages>
    <package name="SampleConsoleNet8" line-rate="0.8887999999999999" branch-rate="0.5" complexity="3">
      <classes>
        <class name="SampleConsoleNet8.SampleHelper" filename="----/SampleHelper.cs" line-rate="0.8887999999999999" branch-rate="0.5" complexity="3">
          <methods>
           <!-- 省略 -->
          </methods>
          <lines>
            <!-- 省略 -->
          </lines>
        </class>
      </classes>
    </package>
  </packages>
</coverage>