web-dev-qa-db-ja.com

複雑なパラメーターを[理論]に渡す

Xunitには素晴らしい機能がありますTheory属性を使用して1つのテストを作成し、InlineData属性にデータを入れると、xUnitは多くのテストを生成し、それらすべてをテストできます。

このようなものが欲しいのですが、メソッドのパラメーターは「単純なデータ」ではなく(stringintdoubleなど)、クラスのリストです:

public static void WriteReportsToMemoryStream(
    IEnumerable<MyCustomClass> listReport,
    MemoryStream ms,
    StreamWriter writer) { ... }
79
zchpit

XUnitには多くのxxxxData属性があります。たとえば、PropertyData属性を確認してください。

IEnumerable<object[]>を返すプロパティを実装できます。このメソッドが生成する各object[]は、[Theory]メソッドへの1回の呼び出しのパラメーターとして「アンパック」されます。

別のオプションはClassDataです。これは同じように機能しますが、異なるクラス/名前空間のテスト間で「ジェネレータ」を簡単に共有でき、「データジェネレータ」を実際のテストメソッドから分離することもできます。

すなわち ここからのこれらの例

PropertyDataの例

public class StringTests2
{
    [Theory, PropertyData(nameof(SplitCountData))]
    public void SplitCount(string input, int expectedCount)
    {
        var actualCount = input.Split(' ').Count();
        Assert.Equal(expectedCount, actualCount);
    }

    public static IEnumerable<object[]> SplitCountData
    {
        get
        {
            // Or this could read from a file. :)
            return new[]
            {
                new object[] { "xUnit", 1 },
                new object[] { "is fun", 2 },
                new object[] { "to test with", 3 }
            };
        }
    }
}

ClassDataの例

public class StringTests3
{
    [Theory, ClassData(typeof(IndexOfData))]
    public void IndexOf(string input, char letter, int expected)
    {
        var actual = input.IndexOf(letter);
        Assert.Equal(expected, actual);
    }
}

public class IndexOfData : IEnumerable<object[]>
{
    private readonly List<object[]> _data = new List<object[]>
    {
        new object[] { "hello world", 'w', 6 },
        new object[] { "goodnight moon", 'w', -1 }
    };

    public IEnumerator<object[]> GetEnumerator()
    { return _data.GetEnumerator(); }

    IEnumerator IEnumerable.GetEnumerator()
    { return GetEnumerator(); }
}
122
quetzalcoatl

@Quetzalcoatlの答えを更新するには:属性[PropertyData]は、[MemberData]を返す静的メソッド、フィールド、またはプロパティの文字列名を引数として取るIEnumerable<object[]>に置き換えられました。 (実際にcalculateテストケースを1つずつ実行し、計算されたとおりにそれらを生成できるイテレータメソッドがあることは特に素晴らしいと思います。)

列挙子によって返されるシーケンスの各要素はobject[]であり、各配列は同じ長さである必要があり、その長さはテストケースへの引数の数である必要があります(属性[MemberData]および各要素対応するメソッドパラメーターと同じ型である必要があります(または、変換可能な型である可能性がありますが、わかりません)。

2014年3月のxUnit.netリリースノート および サンプルコードを含む実際のパッチ を参照してください。)

35
davidbak

匿名オブジェクト配列の作成は、データを構築する最も簡単な方法ではないため、プロジェクトでこのパターンを使用しました

最初に、再利用可能な共有クラスを定義します

//http://stackoverflow.com/questions/22093843
public interface ITheoryDatum
{
    object[] ToParameterArray();
}

public abstract class TheoryDatum : ITheoryDatum
{
    public abstract object[] ToParameterArray();

    public static ITheoryDatum Factory<TSystemUnderTest, TExpectedOutput>(TSystemUnderTest sut, TExpectedOutput expectedOutput, string description)
    {
        var datum= new TheoryDatum<TSystemUnderTest, TExpectedOutput>();
        datum.SystemUnderTest = sut;
        datum.Description = description;
        datum.ExpectedOutput = expectedOutput;
        return datum;
    }
}

public class TheoryDatum<TSystemUnderTest, TExecptedOutput> : TheoryDatum
{
    public TSystemUnderTest SystemUnderTest { get; set; }

    public string Description { get; set; }

    public TExpectedOutput ExpectedOutput { get; set; }

    public override object[] ToParameterArray()
    {
        var output = new object[3];
        output[0] = SystemUnderTest;
        output[1] = ExpectedOutput;
        output[2] = Description;
        return output;
    }

}

これで、個々のテストとメンバーのデータが記述しやすくなり、きれいになりました...

public class IngredientTests : TestBase
{
    [Theory]
    [MemberData(nameof(IsValidData))]
    public void IsValid(Ingredient ingredient, string testDescription, bool expectedResult)
    {
        Assert.True(ingredient.IsValid == expectedResult, testDescription);
    }

    public static IEnumerable<object[]> IsValidData
    {
        get
        {
            var food = new Food();
            var quantity = new Quantity();
            var data= new List<ITheoryDatum>();

            data.Add(TheoryDatum.Factory(new Ingredient { Food = food }                       , false, "Quantity missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity }               , false, "Food missing"));
            data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity, Food = food }  , true,  "Valid"));

            return data.ConvertAll(d => d.ToParameterArray());
        }
    }
}

文字列Descriptionプロパティは、多くのテストケースの1つが失敗したときにボーンをスローすることです。

9
fiat

Manufacturerクラスを持つ複雑なCarクラスがあるとします:

public class Car
{
     public int Id { get; set; }
     public long Price { get; set; }
     public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
    public string Name { get; set; }
    public string Country { get; set; }
}

Carクラスを埋めて、理論テストに渡します。

したがって、以下のようにCarクラスのインスタンスを返す 'CarClassData'クラスを作成します。

public class CarClassData : IEnumerable<object[]>
    {
        public IEnumerator<object[]> GetEnumerator()
        {
            yield return new object[] {
                new Car
                {
                  Id=1,
                  Price=36000000,
                  Manufacturer = new Manufacturer
                  {
                    Country="country",
                    Name="name"
                  }
                }
            };
        }
        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }

テストメソッド(CarTest)を作成し、車をパラメーターとして定義します。

[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
     var output = car;
     var result = _myRepository.BuyCar(car);
}

complex type in theory

幸運

3
Iman Bahrampour

あなたはこの方法を試すことができます:

public class TestClass {

    bool isSaturday(DateTime dt)
    {
       string day = dt.DayOfWeek.ToString();
       return (day == "Saturday");
    }

    [Theory]
    [MemberData("IsSaturdayIndex", MemberType = typeof(TestCase))]
    public void test(int i)
    {
       // parse test case
       var input = TestCase.IsSaturdayTestCase[i];
       DateTime dt = (DateTime)input[0];
       bool expected = (bool)input[1];

       // test
       bool result = isSaturday(dt);
       result.Should().Be(expected);
    }   
}

テストデータを保持する別のクラスを作成します。

public class TestCase
{
   public static readonly List<object[]> IsSaturdayTestCase = new List<object[]>
   {
      new object[]{new DateTime(2016,1,23),true},
      new object[]{new DateTime(2016,1,24),false}
   };

   public static IEnumerable<object[]> IsSaturdayIndex
   {
      get
      {
         List<object[]> tmp = new List<object[]>();
            for (int i = 0; i < IsSaturdayTestCase.Count; i++)
                tmp.Add(new object[] { i });
         return tmp;
      }
   }
}
3
Sandy_Vu

必要に応じて、いくつかのテストで一連の「テストユーザー」を実行したかっただけですが、[ClassData]などは必要なものに対してはやり過ぎのように見えました(アイテムのリストは各テストにローカライズされていたため)。

だから私は、テスト内の配列で、外部からインデックス付けされた以下を行いました:

[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Account_ExistingUser_CorrectPassword(int userIndex)
{
    // DIFFERENT INPUT DATA (static fake users on class)
    var user = new[]
    {
        EXISTING_USER_NO_MAPPING,
        EXISTING_USER_MAPPING_TO_DIFFERENT_EXISTING_USER,
        EXISTING_USER_MAPPING_TO_SAME_USER,
        NEW_USER

    } [userIndex];

    var response = await Analyze(new CreateOrLoginMsgIn
    {
        Username = user.Username,
        Password = user.Password
    });

    // expected result (using ExpectedObjects)
    new CreateOrLoginResult
    {
        AccessGrantedTo = user.Username

    }.ToExpectedObject().ShouldEqual(response);
}

テストの意図を明確に保ちながら、これは私の目標を達成しました。インデックスの同期を保つ必要があるだけですが、それだけです。

結果は素敵に見えますが、折りたたみ可能で、エラーが発生した場合は特定のインスタンスを再実行できます。

enter image description here

0
Simon_Weaver