xUnit
で MemberData
や ClassData
を使ってテストケースを作成した場合、 Visual Studio のテストエクスプローラーでは複数のテストケースが単一のテストケースとして表示されてしまいます。
これだとデバッグが非常にやりにくいので、InlineData
と同じように複数のテストケースとして表示する方法がないか調べてみると、以下の記事が見つかりました。
Visual Studio のテストエクスプローラーで各テストケースを別々の項目として表示するためには、テストケースの型に IXunitSerializable
を実装する必要があるみたいです。
環境
- Visual Studio 2017
- .NET Core 2.2
- xUnit 2.4.1
サンプル
変更前
例えば、MemberData
を使ったテストコードは以下のような感じになっていると思います。
[Theory,
MemberData(nameof(TestData))]
public void TestMethod(int param1, string param2)
{
// Do something test
}
public static IEnumerable<object[]> TestData()
{
yield return new object[] { 0, "hoge" };
yield return new object[] { 1, "fuga" };
}
変更後
IXunitSerializable
を実装するためにテストデータクラス TestCaseData
を作成します。
[Theory,
MemberData(nameof(TestData))]
public void TestMethod(TestCaseData testCaseData)
{
var param1 = testCaseData.Param1;
var param2 = testCaseData.Param2;
// Do something test
}
public class TestCaseData : IXunitSerializable
{
public int Param1 { get; set; }
public string Param2 { get; set; }
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue(nameof(Param1), Param1.ToString());
info.AddValue(nameof(Param2), Param2.ToString());
}
public void Deserialize(IXunitSerializationInfo info) { }
}
public static IEnumerable<object[]> TestData()
{
yield return new object[] {
new TestCaseData{
Param1 = 0,
Param2 = "hoge"
}
};
yield return new object[] {
new TestCaseData{
Param1 = 1,
Param2 = "fuga"
}
};
}
結構面倒です。
もう少し汎用的になるように改良してみる
上記のままだと、テストケースの度に IXunitSerializable
を実装する必要があり面倒なので、何とかして共通化できないかと思い、とりあえず以下の2案が思い浮かびました。
ジェネリックを使って汎用的に実装する
IXunitSerializable
を実装した ValidateTestCase
を作成し、ジェネリックでテストケースクラスの型を指定してやります。
[Theory,
MemberData(nameof(TestData))]
public void TestMethod(ValidateTestCase<TestCaseData> testCaseData)
{
var param1 = testCaseData.Param.Param1;
var param2 = testCaseData.Param.Param2;
// Do something test
}
public class ValidateTestCase<T> : IXunitSerializable
{
public T Param { get; set; }
public void Serialize(IXunitSerializationInfo info)
{
// JSON を使う場合
info.AddValue(nameof(Param), JsonConvert.SerializeObject(Param));
// ハッシュ値を使う場合
// info.AddValue(nameof(Param), GetHashCode());
// GUID を使う場合
// info.AddValue(nameof(Param), Guid.NewGuid().ToString());
}
public void Deserialize(IXunitSerializationInfo info) { }
}
public class TestCaseData
{
public int Param1 { get; set; }
public string Param2 { get; set; }
}
public static IEnumerable<object[]> TestData()
{
yield return new object[] {
new ValidateTestCase<TestCaseData>{
Param = new TestCaseData(){
Param1 = 0,
Param2 = "hoge"
}
}
};
yield return new object[] {
new ValidateTestCase<TestCaseData>{
Param = new TestCaseData(){
Param1 = 1,
Param2 = "fuga"
}
}
};
}
Serialize
メソッドで登録する文字列を ToString()
から JSON でのシリアライズに変更しているのがポイントです。
クラスに対して ToString()
するとクラス名が出力されるので、全てのテストで同じ文字列になってしまい、テストエクスプローラーでは単一のテストとして表示されてしまいました。
この文字列は一連のテストケースにおいてユニークでないとダメなようです。
なので、テストパラメーターには重複はないという前提で、JSON でシリアライズしてみました。
ただし、テストケースクラス TestCaseData
にデリゲートを含んでいる場合は上手くいかなかったので、そういう場合はハッシュ値や GUID などを使ったほうが良いと思います。
(ハッシュ値は被ることもあるかもしれないので、GUID のほうが確実か?)
抽象クラスを作成して汎用的に実装する
上記だと毎回 ValidateTestCase
と T
の2つのクラスをインスタンス化しないといけなくてちょっと面倒なので、同じような感じで抽象クラスで実装してみました。
自クラス this
に対してシリアライズを行っています。
[Theory,
MemberData(nameof(TestData))]
public void TestMethod(TestCaseData testCaseData)
{
var param1 = testCaseData.Param1;
var param2 = testCaseData.Param2;
// Do something test
}
public abstract class XunitSerializableBase : IXunitSerializable
{
public void Serialize(IXunitSerializationInfo info)
{
// JSON を使う場合
info.AddValue(nameof(XunitSerializableBase), JsonConvert.SerializeObject(Param));
// ハッシュ値を使う場合
// info.AddValue(nameof(XunitSerializableBase), GetHashCode());
// GUID を使う場合
// info.AddValue(nameof(XunitSerializableBase), Guid.NewGuid().ToString());
}
public void Deserialize(IXunitSerializationInfo info) { }
}
public class TestCaseData : XunitSerializableBase
{
public int Param1 { get; set; }
public string Param2 { get; set; }
}
public static IEnumerable<object[]> TestData()
{
yield return new object[] {
new TestCaseData{
Param1 = 0,
Param2 = "hoge"
}
};
yield return new object[] {
new TestCaseData{
Param1 = 1,
Param2 = "fuga"
}
};
}
ちょっとだけ記述量が減りました。
他の案は?
上記のリンク先にある DjvuTheory
が便利そうだったので試しましたが、.NET Core 2.2 だと動きませんでした。
自分で修正しようともしてみましたが、何故かうまくいかず断念。
これが使えたら Theory
をDjvuTheory
に置換するだけでいけそうだったのに残念です。
参考 URL
- https://stackoverflow.com/questions/30574322/memberdata-tests-show-up-as-one-test-instead-of-many
- https://darchuk.net/2019/04/12/serializing-xunit-test-cases/
- https://docs.microsoft.com/ja-jp/azure/bot-service/unit-test-bots?view=azure-bot-service-4.0&tabs=csharp
- https://github.com/mysteryx93/XunitDjvuTheory