【C#】MemberData属性でテストデータを外部メソッドから供給する

目次

概要

xUnit.netの [MemberData] 属性を使用し、テストメソッドに渡すパラメータを静的メソッド(またはプロパティ)から動的に供給する実装です。 [InlineData] では扱いにくい「配列」「オブジェクト」「動的に生成されるデータ」や「多数のテストケース」を管理するのに適しています。

仕様(入出力)

  • データ供給元: IEnumerable<object[]> を返す public static メソッドを作成します。
  • テストメソッド: [MemberData] 属性で供給元メソッドの名前を指定し、引数でデータを受け取ります。
  • 動作: 供給されたデータの件数分、テストメソッドが繰り返し実行されます。

基本の使い方

// データ供給メソッド
public static IEnumerable<object[]> GetNames()
{
    // { 期待値, 入力1, 入力2 }
    yield return new object[] { "John Doe", "John", "Doe" };
    yield return new object[] { "Jane Smith", "Jane", "Smith" };
}

[Theory]
[MemberData(nameof(GetNames))]
public void NameTest(string expected, string first, string last)
{
    // ...
}

コード全文

氏名(First Name, Last Name)を結合してフルネームを生成するメソッドに対し、複数の名前パターンを検証するコードです。

using System;
using System.Collections.Generic;
using Xunit;

namespace ParameterizedTests
{
    public class MemberDataSample
    {
        /// <summary>
        /// テストデータを提供する静的メソッド
        /// 戻り値は IEnumerable<object[]> である必要があります。
        /// </summary>
        public static IEnumerable<object[]> NameData()
        {
            // object配列の中身が、テストメソッドの引数(expected, first, last)にマッピングされます
            yield return new object[] { "Taro Yamada", "Taro", "Yamada" };
            yield return new object[] { "John Doe", "John", "Doe" };
            yield return new object[] { "Tech User", "Tech", "User" };
        }

        /// <summary>
        /// MemberDataで指定したメソッドからデータを取得して実行
        /// </summary>
        [Theory]
        [MemberData(nameof(NameData))]
        public void GetFullName_ReturnsCombinedString(string expected, string first, string last)
        {
            // Arrange
            var generator = new NameGenerator();

            // Act
            var result = generator.GetFullName(first, last);

            // Assert
            Assert.Equal(expected, result);
        }
    }

    /// <summary>
    /// テスト対象のクラス
    /// </summary>
    public class NameGenerator
    {
        public string GetFullName(string firstName, string lastName)
        {
            return $"{firstName} {lastName}";
        }
    }
}

カスタムポイント

  • プロパティの使用: メソッドだけでなく、静的プロパティもデータソースとして指定できます。C#public static TheoryData<string, string, string> TestCases => new TheoryData<string, string, string> { { "A B", "A", "B" }, { "X Y", "X", "Y" } }; TheoryData クラスを使うと型安全にデータを定義できます。
  • 外部クラスの利用: テストデータ定義を別クラスに分離したい場合は、MemberType プロパティを使用します。 [MemberData(nameof(ExternalData.GetData), MemberType = typeof(ExternalData))]

注意点

  1. 静的メンバであること: データを提供するメソッドやプロパティは必ず static である必要があります。
  2. 型と順序の一致: new object[] { ... } の中に記述するデータの「型」と「並び順」は、テストメソッドの引数定義と完全に一致させてください。不一致の場合、実行時に型変換エラーが発生します。
  3. 可視性: データ提供メンバは基本的に public である必要があります。

応用

引数付きMemberDataによる動的生成

データ生成メソッド自体にパラメータを渡し、取得するテストデータの件数や範囲を制御する例です。

public static IEnumerable<object[]> GetRange(int count)
{
    for (int i = 1; i <= count; i++)
    {
        yield return new object[] { i };
    }
}

// 5件のデータを生成してテスト
[Theory]
[MemberData(nameof(GetRange), parameters: 5)]
public void CheckPositiveNumbers(int number)
{
    Assert.True(number > 0);
}

まとめ

[MemberData] を利用することで、テストロジック([Theory]メソッド)とテストデータ(staticメソッド)を分離できます。これにより、データの追加・変更が容易になり、特に複雑なオブジェクトや大量のパターンを検証する際にテストコードの可読性を高く保つことができます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次