MSTest でのデータ ドリブン テスト

データ ドリブン テストでは、複数の入力データ セットで同じテスト メソッドを実行できます。 テスト ケースごとに個別のテスト メソッドを記述する代わりに、テスト ロジックを 1 回定義し、属性または外部データ ソースを介して異なる入力を提供します。

概要

MSTest には、データ ドリブン テストに関するいくつかの属性が用意されています。

特性 利用シーン 最適な用途
DataRow インライン テスト データ 単純で静的なテスト ケース
DynamicData メソッド、プロパティ、またはフィールドからのデータ 複雑なテストデータおよび計算済みのテストデータ
DataSource 外部データ ファイルまたはデータベース 外部データ ソースを使用した従来のシナリオ

MSTest には、データ ドリブン シナリオを拡張するための次の種類も用意されています。

  • TestDataRow<T>: 表示名、カテゴリ、個々のテスト ケースへのメッセージの無視などのメタデータ サポートを追加する ITestDataSource 実装 ( DynamicDataを含む) の戻り値の型。
  • ITestDataSource: カスタム属性に実装して、完全なカスタム データ ソース属性を作成できるインターフェイス。

ヒント

組み合わせテスト (複数のパラメーター セットのすべての組み合わせをテストする) には、オープンソースの Combinatorial.MSTest NuGet パッケージを使用します。 このコミュニティで管理されるパッケージは GitHub で入手できますが 、Microsoft では管理されていません。

DataRowAttribute

DataRowAttributeを使用すると、複数の異なる入力で同じテスト メソッドを実行できます。 DataRowで修飾されたテスト メソッドに 1 つ以上のTestMethodAttribute属性を適用します。

引数の数と型は、テスト メソッドのシグネチャと完全に一致している必要があります。

ヒント

関連するアナライザー:

  • MSTEST0014 は、 DataRow 引数がテスト メソッドシグネチャと一致することを検証します。
  • MSTEST0042 は、同じテスト ケースを複数回実行する重複する DataRow エントリを検出します。

基本的な使用方法

[TestClass]
public class CalculatorTests
{
    [TestMethod]
    [DataRow(1, 2, 3)]
    [DataRow(0, 0, 0)]
    [DataRow(-1, 1, 0)]
    [DataRow(100, 200, 300)]
    public void Add_ReturnsCorrectSum(int a, int b, int expected)
    {
        var calculator = new Calculator();
        Assert.AreEqual(expected, calculator.Add(a, b));
    }
}

サポートされている引数の型

DataRow では、プリミティブ、文字列、配列、null 値など、さまざまな引数型がサポートされています。

[TestClass]
public class DataRowExamples
{
    [TestMethod]
    [DataRow(1, "message", true, 2.0)]
    public void TestWithMixedTypes(int i, string s, bool b, float f)
    {
        // Test with different primitive types
    }

    [TestMethod]
    [DataRow(new string[] { "line1", "line2" })]
    public void TestWithArray(string[] lines)
    {
        Assert.AreEqual(2, lines.Length);
    }

    [TestMethod]
    [DataRow(null)]
    public void TestWithNull(object o)
    {
        Assert.IsNull(o);
    }

    [TestMethod]
    [DataRow(new string[] { "a", "b" }, new string[] { "c", "d" })]
    public void TestWithMultipleArrays(string[] input, string[] expected)
    {
        // Starting with MSTest v3, two arrays don't need wrapping
    }
}

変数引数にパラメーターを使用する

params キーワードを使用して、可変個の引数を受け入れます。

[TestClass]
public class ParamsExample
{
    [TestMethod]
    [DataRow(1, 2, 3, 4)]
    [DataRow(10, 20)]
    [DataRow(5)]
    public void TestWithParams(params int[] values)
    {
        Assert.IsTrue(values.Length > 0);
    }
}

カスタム表示名

DisplayName プロパティを設定して、テスト エクスプローラーでのテスト ケースの表示方法をカスタマイズします。

[TestClass]
public class DisplayNameExample
{
    [TestMethod]
    [DataRow(1, 2, DisplayName = "Functional Case FC100.1")]
    [DataRow(3, 4, DisplayName = "Edge case: small numbers")]
    public void TestMethod(int i, int j)
    {
        Assert.IsTrue(i < j);
    }
}

ヒント

テスト メタデータをより詳細に制御する場合は、< を使用することを検討してください。 TestDataRow<T> は、テスト カテゴリと共に表示名をサポートし、個々のテスト ケースのメッセージを無視します。

特定のテスト ケースを無視する

MSTest v3.8 以降では、 IgnoreMessage プロパティを使用して特定のデータ行をスキップします。

[TestClass]
public class IgnoreDataRowExample
{
    [TestMethod]
    [DataRow(1, 2)]
    [DataRow(3, 4, IgnoreMessage = "Temporarily disabled - bug #123")]
    [DataRow(5, 6)]
    public void TestMethod(int i, int j)
    {
        // Only the first and third data rows run
        // The second is skipped with the provided message
    }
}

DynamicDataAttribute

DynamicDataAttributeを使用すると、メソッド、プロパティ、またはフィールドからテスト データを提供できます。 テスト データが複雑な場合、動的に計算される場合、またはインライン DataRow 属性に対して詳細すぎる場合は、この属性を使用します。

サポートされているデータ ソースの種類

データ ソースは、IEnumerable<T>が次の表に示す型のいずれかである任意のTを返すことができます。 IEnumerable<T>List<T>などの配列、カスタム コレクション型など、T[]を実装するすべてのコレクションが機能します。 ニーズに基づいて選択します。

戻り値の型 型安全性 メタデータのサポート 最適な用途
ValueTuple (例: (int, string)) コンパイル時 いいえ ほとんどのシナリオ - 完全な型チェックを使用した単純な構文
Tuple<...> コンパイル時 いいえ ValueTuple を使用できない場合
TestDataRow<T> コンパイル時 イエス 表示名、カテゴリ、または無視メッセージを設定する必要があるテストケース
object[] ランタイムのみ いいえ 新しいテストでは既存コードを避ける

ヒント

新しいテスト データ メソッドの場合は、単純なケースに ValueTuple を使用するか、メタデータが必要な場合に TestDataRow<T> します。 コンパイル時の型チェックがなく、型の不一致によってランタイム エラーが発生する可能性があるため、 object[] は避けてください。

データ ソース

データ ソースには、メソッド、プロパティ、またはフィールドを指定できます。 3 つはすべて交換可能です。好みに応じて選択します。

[TestClass]
public class DynamicDataExample
{
    // Method - best for computed or yielded data
    public static IEnumerable<(int Value, string Name)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }

    // Property - concise for static data
    public static IEnumerable<(int Value, string Name)> TestDataProperty =>
    [
        (1, "first"),
        (2, "second")
    ];

    // Field - simplest for static data
    public static IEnumerable<(int Value, string Name)> TestDataField =
    [
        (1, "first"),
        (2, "second")
    ];

    [TestMethod]
    [DynamicData(nameof(GetTestData))]
    public void TestWithMethod(int value, string name)
    {
        Assert.IsTrue(value > 0);
    }

    [TestMethod]
    [DynamicData(nameof(TestDataProperty))]
    public void TestWithProperty(int value, string name)
    {
        Assert.IsTrue(value > 0);
    }

    [TestMethod]
    [DynamicData(nameof(TestDataField))]
    public void TestWithField(int value, string name)
    {
        Assert.IsTrue(value > 0);
    }
}

データ ソースのメソッド、プロパティ、フィールドは public static し、サポートされている型の IEnumerable<T> を返す必要があります。

ヒント

関連アナライザー: MSTEST0018 は、データ ソースが存在し、アクセス可能であり、正しい署名を持っていることを検証します。

別のクラスのデータ ソース

型パラメーターを使用して別のクラスを指定します。

public class TestDataProvider
{
    public static IEnumerable<(int, string)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }
}

[TestClass]
public class DynamicDataExternalExample
{
    [TestMethod]
    [DynamicData(nameof(TestDataProvider.GetTestData), typeof(TestDataProvider))]
    public void TestMethod(int value1, string value2)
    {
        Assert.IsTrue(value1 > 0);
    }
}

カスタム表示名

DynamicDataDisplayName プロパティを使用してテスト ケースの表示名をカスタマイズします。

using System.Reflection;

[TestClass]
public class DynamicDataDisplayNameExample
{
    [TestMethod]
    [DynamicData(nameof(GetTestData), DynamicDataDisplayName = nameof(GetDisplayName))]
    public void TestMethod(int value1, string value2)
    {
        Assert.IsTrue(value1 > 0);
    }

    public static IEnumerable<(int, string)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }

    public static string GetDisplayName(MethodInfo methodInfo, object[] data)
    {
        return $"{methodInfo.Name} with value {data[0]} and '{data[1]}'";
    }
}

表示名メソッドは public staticし、 stringを返し、 MethodInfoobject[]の 2 つのパラメーターを受け入れる必要があります。

ヒント

カスタム表示名に対するより簡単な方法については、別のメソッドではなく、< プロパティで > を使用することを検討してください。

データ ソースからのすべてのテスト ケースを無視する

MSTest v3.8 以降では、 IgnoreMessage を使用してすべてのテスト ケースをスキップします。

[TestClass]
public class IgnoreDynamicDataExample
{
    [TestMethod]
    [DynamicData(nameof(GetTestData), IgnoreMessage = "Feature not ready")]
    public void TestMethod(int value1, string value2)
    {
        // All test cases from GetTestData are skipped
    }

    public static IEnumerable<(int, string)> GetTestData()
    {
        yield return (1, "first");
        yield return (2, "second");
    }
}

ヒント

個々のテスト ケースを無視するには、TestDataRow<T> プロパティでIgnoreMessageを使用します。 TestDataRow<T> セクションを参照してください。

TestDataRow

TestDataRow<T> クラスは、データ ドリブン テストのテスト データの制御を強化します。 データ ソースの戻り値の型としてIEnumerable<T>TestDataRow<T>を使用して、次を指定します。

  • カスタム表示名: DisplayName プロパティを使用して、テスト ケースごとに一意の表示名を設定します。
  • テスト カテゴリ: TestCategories プロパティを使用して、個々のテスト ケースにメタデータをアタッチします。
  • メッセージを無視する: IgnoreMessage プロパティを使用する理由で、特定のテスト ケースをスキップします。
  • 型セーフ データ: 厳密に型指定されたテスト データにジェネリックを使用します。

基本的な使用方法

[TestClass]
public class TestDataRowExample
{
    [TestMethod]
    [DynamicData(nameof(GetTestDataRows))]
    public void TestMethod(int value1, string value2)
    {
        Assert.IsTrue(value1 > 0);
    }

    public static IEnumerable<TestDataRow<(int, string)>> GetTestDataRows()
    {
        yield return new TestDataRow<(int, string)>((1, "first"))
        {
            DisplayName = "Test Case 1: Basic scenario",
        };

        yield return new TestDataRow<(int, string)>((2, "second"))
        {
            DisplayName = "Test Case 2: Edge case",
            TestCategories = ["HighPriority", "Critical"],
        };

        yield return new TestDataRow<(int, string)>((3, "third"))
        {
            IgnoreMessage = "Not yet implemented",
        };
    }
}

DataSourceAttribute

DataSource は .NET Framework でのみ使用できます。 .NET (Core) プロジェクトの場合は、代わりに DataRow または DynamicData を使用します。

DataSourceAttributeは、CSV ファイル、XML ファイル、データベースなどの外部データ ソースにテストを接続します。

詳細については、以下を参照してください。

ITestDataSource

ITestDataSource インターフェイスを使用すると、完全にカスタムのデータ ソース属性を作成できます。 環境変数、構成ファイル、またはその他のランタイム条件に基づくテスト データの生成など、組み込みの属性でサポートされていない動作が必要な場合は、このインターフェイスを実装します。

インターフェイス メンバー

インターフェイスでは、次の 2 つのメソッドを定義します。

メソッド 目的
GetData(MethodInfo) テスト データを次のように返します。 IEnumerable<object?[]>
GetDisplayName(MethodInfo, object?[]?) テスト ケースの表示名を返します。

カスタム データ ソース属性の作成

カスタム データ ソースを作成するには、 Attribute から継承し、 ITestDataSourceを実装する属性クラスを定義します。

using System.Globalization;
using System.Reflection;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class MyDataSourceAttribute : Attribute, ITestDataSource
{
    public IEnumerable<object?[]> GetData(MethodInfo methodInfo)
    {
        // Return test data based on your custom logic
        yield return [1, "first"];
        yield return [2, "second"];
        yield return [3, "third"];
    }

    public string? GetDisplayName(MethodInfo methodInfo, object?[]? data)
    {
        return data is null
            ? null
            : string.Format(CultureInfo.CurrentCulture, "{0} ({1})", methodInfo.Name, string.Join(",", data));
    }
}

[TestClass]
public class CustomDataSourceExample
{
    [TestMethod]
    [MyDataSource]
    public void TestWithCustomDataSource(int value, string name)
    {
        Assert.IsTrue(value > 0);
        Assert.IsNotNull(name);
    }
}

実際の例: 環境ベースのテスト データ

この例では、ターゲット フレームワークに基づいてテスト データを生成し、オペレーティング システムに基づいてフィルター処理するカスタム属性を示します。

using System.Globalization;
using System.Reflection;

[AttributeUsage(AttributeTargets.Method)]
public class TargetFrameworkDataAttribute : Attribute, ITestDataSource
{
    private readonly string[] _frameworks;

    public TargetFrameworkDataAttribute(params string[] frameworks)
    {
        _frameworks = frameworks;
    }

    public IEnumerable<object?[]> GetData(MethodInfo methodInfo)
    {
        bool isWindows = OperatingSystem.IsWindows();

        foreach (string framework in _frameworks)
        {
            // Skip .NET Framework on non-Windows platforms
            if (!isWindows && framework.StartsWith("net4", StringComparison.Ordinal))
            {
                continue;
            }

            yield return [framework];
        }
    }

    public string? GetDisplayName(MethodInfo methodInfo, object?[]? data)
    {
        return data is null
            ? null
            : string.Format(CultureInfo.CurrentCulture, "{0} ({1})", methodInfo.Name, data[0]);
    }
}

[TestClass]
public class CrossPlatformTests
{
    [TestMethod]
    [TargetFrameworkData("net48", "net8.0", "net9.0")]
    public void TestOnMultipleFrameworks(string targetFramework)
    {
        // Test runs once per applicable framework
        Assert.IsNotNull(targetFramework);
    }
}

ヒント

表示名をカスタマイズしたりメタデータを追加したりするだけの単純なケースでは、<を実装する代わりに> を使用することを検討してください。 動的フィルター処理または複雑なデータ生成ロジックを必要とするシナリオに対して、カスタム ITestDataSource 実装を予約します。

展開戦略

データ ドリブン テスト属性は、テスト エクスプローラーと TRX 結果でのテスト ケースの表示方法を制御する TestDataSourceUnfoldingStrategy プロパティをサポートします。 このプロパティは、個々のテスト ケースを個別に実行できるかどうかを決定します。

検出と実行のフェーズ

MSTest は、データドリブン テストを 2 つの異なるフェーズで処理します。

  • 検出フェーズ: MSTest は、すべてのデータ ソース属性 (DataRowDynamicDataITestDataSource) を評価して、テスト ケースの一覧を決定します。 この評価は、通常の MSTest ライフサイクル フックのいずれかが実行される 前に 発生します。AssemblyInitializeClassInitialize、その他のセットアップ メソッドはまだ実行されていません。
  • 実行フェーズ: MSTest は、通常のライフサイクル (アセンブリ初期化、クラス初期化、テスト初期化、テスト メソッド、クリーンアップ) を実行し、各テスト ケースとそのデータを実行します。

データ ソースは検出中に評価されるため、データ生成コードは、 AssemblyInitialize または ClassInitializeによって設定された状態に依存できません。 データ ソースがセットアップ ロジックに依存している場合 (たとえば、 ClassInitializeで初期化されたデータベース接続からの読み取り)、データ ソースの評価は検出中に失敗します。

利用可能な戦略

戦略 行動
Auto (既定値) MSTest は、最適な展開戦略を決定します。
Unfold すべてのテスト ケースが個別に展開および表示されます。
Fold すべてのテスト ケースは、1 つのテスト ノードに折りたたまれます。

折りたたみテストと展開テスト

展開戦略は、テスト エクスプローラーと TRX 出力でのテスト結果の報告方法に影響します。

  • 展開されたテスト: 各データ行は、テスト エクスプローラーと TRX に個別の独立したテスト エントリとして表示されます。 個々のテスト ケースを実行、デバッグ、またはフィルター処理できます。 各エントリには、独自の成功/失敗状態があります。
  • 折りたたまれたテスト: すべてのデータ行は、テスト エクスプローラーで 1 つのテスト ノードとして表示されます。 TRX に 1 つのエントリが表示され、その 1 つのテスト ケースに複数の結果が関連付けられます。 個々のデータ行を個別に実行またはフィルター処理することはできません。

発見中の例外処理が折りたたみを引き起こす

MSTest がデータ ソースの探索中に評価を行い、その評価で例外が発生した場合、設定された展開戦略に関わらずテストは折りたたまれた状態に戻ります。 フレームワークは個々のテスト ケースを列挙できないため、テスト メソッドを 1 つの (折りたたまれた) エントリとして登録します。

実行時に、MSTest は通常のライフサイクルの一部としてデータ ソースを再評価します。 セットアップ状態の不足 (たとえば、 ClassInitialize が提供する依存関係) によって例外が発生した場合、ライフサイクル フックが実行されたため、実行中にデータ ソースが成功する可能性があります。

検出中にスローされるテストをデバッグすると、テストの開始前に例外 (たとえば、NullReferenceException のような) が表示されることがあります。 この例外は、検出フェーズの評価に起因します。 デバッガーで Continue キー を押します。実行フェーズでは初期化メソッドを含む完全な MSTest ライフサイクルが実行されるため、テストは正常に実行されます。 詳細については、 microsoft/testfx#7774 を参照してください。

戦略を変更するタイミング

ほとんどのシナリオでは、既定の Auto 動作が最適なバランスを提供します。 特定の要件がある場合は、展開戦略を変更することを検討してください。

  • データ ソースが、検出中に使用できないランタイム状態またはセットアップ ロジックに依存している場合は、 Fold を使用します。
  • 各評価で異なる値を返す非決定的なデータ ソースには、 Fold を使用します。
  • Foldを使用して、多数のテスト ケースでパフォーマンスが問題になる場合のオーバーヘッドを軽減します。

使用例

[TestClass]
public class UnfoldingExample
{
    [TestMethod(UnfoldingStrategy = TestDataSourceUnfoldingStrategy.Unfold)] // That's the default behavior
    [DataRow(1, "one")]
    [DataRow(2, "two")]
    [DataRow(3, "three")]
    public void TestMethodWithUnfolding(int value, string text)
    {
        // Each test case appears individually in Test Explorer
    }

    [TestMethod(UnfoldingStrategy = TestDataSourceUnfoldingStrategy.Fold)]
    [DataRow(1, "one")]
    [DataRow(2, "two")]
    [DataRow(3, "three")]
    public void TestMethodWithFolding(int value, string text)
    {
        // All test cases appear as a single collapsed node
    }
}

ベスト プラクティス

  • 適切な属性を選択します。単純なインライン データには DataRow を使用します。 複雑なデータまたは計算されたデータには DynamicData を使用します。
  • テスト ケースに名前を付ける: DisplayName を使用して、テストの失敗を識別しやすくします。
  • データ ソースを閉じる: 可能な場合は同じクラスでデータ ソースを定義し、保守容易性を高めます。
  • 意味のあるデータを使用する: エッジ ケースと境界条件を実行するテスト データを選択します。
  • 組み合わせテストを検討する: パラメーターの組み合わせをテストする場合は、 Combinatorial.MSTest パッケージを使用します。

こちらも参照ください