xUnitのパラメーターライズドテストでも型をいい感じにしたい
いまさらなめちゃくちゃ備忘録
xUnitでパラメーターライズドテスト(テーブル駆動テスト)を書くときは [Theory] アトリビュートと [InlineData], [MemberData], [ClassData] を使って書ける。以下は [InlineData]を書くパターン。
public class AdderTest
{
[Theory]
[InlineData(3, 4, 7)]
[InlineData(-2, 2, 0)]
public void TestAdd(int a, int b, int expected)
{
var adder = new Adder();
var result = adder.Add(a, b);
Assert.Equal(expected, result);
}
}
しかしながらちょっと計算で入力などを作りたくなると [InlineData] ではできなくなる
public class AdderTest
{
[Theory]
[InlineData(Math.Abs(3), 4, 7)]
public void TestAdd(int a, int b, int expected)
{
var adder = new Adder();
var result = adder.Add(a, b);
Assert.Equal(expected, result);
}
}
こういうときは [MemberData] や [ClassData]を使ってやればよい。インターネットを検索すると以下のような例がある。
public class AdderTest
{
[Theory]
[MemberData(nameof(TestAdd2_Data))]
public void TestAdd(int a, int b, int expected)
{
var adder = new Adder();
var result = adder.Add(a, b);
Assert.Equal(expected, result);
}
public static IEnumerable<object[]> TestAdd_Data()
{
yield return new object[] { 5, 5, 10 };
yield return new object[] { -3, -7, -10 };
yield return new object[] { 10, 0, 10 };
}
}
ただこれ TestAdd2_Data のシグネチャが IEnumerable<object[]> なので yield return する object[] に int 以外が入っていてもコンパイルエラーにはならず、実行時まで判明しない。せっかく型があるのにもったいない…。
この件に関していろんな人が似たような課題を感じていて、ggるといろいろ出てくる。それらを見たうえで個人的に書くときは以下のようにして何とかしていた。
public class AdderTest
{
[Theory]
[MemberData(nameof(TestAdd2_Data))]
public void TestAdd2(int a, int b, int expected)
{
var adder = new Adder();
var result = adder.Add(a, b);
Assert.Equal(expected, result);
}
public static IEnumerable<object[]> TestAdd2_Data()
{
return new List<(int, int, int)>
{
(3, 5, 8),
(-3, -6, -9),
(0, 5, 5),
(1.1, 1, 1)
}.Select(t => new object[] { t.Item1, t.Item2, t.Item3 });
}
}
うーん。これでもいいけどなんかスッキリしない気持ち。
TheoryData<T>
api.xunit.net
TheoryData<T>はこういう時に使える便利なもの。これ一体いつからあったんだ…。*1
public class AdderTest
{
[Theory]
[MemberData(nameof(TestAdd_Data))]
public void Test_Add(int a, int b, int expected)
{
var adder = new Adder();
var result = adder.Add(a, b);
Assert.Equal(expected, result);
}
public static TheoryData<int, int, int> TestAdd_Data() => new()
{
{ 1, 2, 3 },
{ -1, -1, -2 },
{ 0, 0, 0 },
};
}
TheoryData<T> であればOKで、以下のように TheoryData<T>を継承した型でも良い
public class AdderTest
{
[Theory]
[ClassData(typeof(TestAdd_DataClass))]
public void Test_Add(int a, int b, int expected)
{
var adder = new Adder();
var result = adder.Add(a, b);
Assert.Equal(expected, result);
}
class TestAdd_DataClass : TheoryData<int, int, int>
{
public TestAdd_DataClass()
{
Add(10, 20, 30);
Add(-5, 5, 0);
Add(100, 200, 300);
}
}
}
良さそう。
まとめ
今更知った TheoryData<T> について備忘録を書いた。これで楽ができそう