将[Theory]
与[InlineData]
一起使用时,它会为所提供的每个内联数据项创建一个测试。但是,如果您使用[MemberData]
,它只会显示为一个测试。
有没有办法让[MemberData]
测试显示为多个测试?
答案 0 :(得分:22)
我花了很多时间试图在我的项目中找到这个。来自@NPadrutt的This related Github discussion本人帮了很多忙,但它仍然令人困惑。
tl; dr就是这样:[MemberInfo]
将报告单个组测试,除非通过实施IXunitSerializable
可以完全序列化和反序列化每个测试的提供对象。< / p>
<强>背景强>
我自己的测试设置如下:
public static IEnumerable<object[]> GetClients()
{
yield return new object[] { new Impl.Client("clientType1") };
yield return new object[] { new Impl.Client("clientType2") };
}
[Theory]
[MemberData(nameof(GetClients))]
public void ClientTheory(Impl.Client testClient)
{
// ... test here
}
对于来自[MemberData]
的每个对象,测试运行两次,如预期的那样。正如@NPadrutt所经历的那样,只有一个项目出现在测试资源管理器中,而不是两个。这是因为提供的对象Impl.Client
不能通过xUnit支持的任何接口进行序列化(稍后会详细介绍)。
在我的情况下,我并不想将测试问题泄露到我的主代码中。我以为我可以在真正的课堂上写一个瘦代理,这会欺骗xUnit跑步者认为它可以序列化它,但是在与它斗争的时间超过我想要承认的时间之后,我意识到我不是这样的部分&#39理解是:
在发现过程中,对象不会被序列化以计算排列;当测试开始时,每个对象在测试运行时反序列化。
因此,您为[MemberData]
提供的任何对象都必须支持完整的往返(de-)序列化。现在这对我来说显而易见,但在我试图解决这个问题时,我无法找到任何文档。
<强>解决方案强>
确保可以完全序列化和反序列化每个对象(以及它可能包含的任何非基元)。实现xUnit&#39; s IXunitSerializable
告诉xUnit它是一个可序列化的对象。
如果在我的情况下,您不想在主代码中添加属性,那么一种解决方案是创建一个可序列化的瘦构建器类来进行测试,该类可以表示重新创建实际类所需的所有内容。在我开始工作之后,上面是上面的代码:
<强> TestClientBuilder 强>
public class TestClientBuilder : IXunitSerializable
{
private string type;
// required for deserializer
public TestClientBuilder()
{
}
public TestClientBuilder(string type)
{
this.type = type;
}
public Impl.Client Build()
{
return new Impl.Client(type);
}
public void Deserialize(IXunitSerializationInfo info)
{
type = info.GetValue<string>("type");
}
public void Serialize(IXunitSerializationInfo info)
{
info.AddValue("type", type, typeof(string));
}
public override string ToString()
{
return $"Type = {type}";
}
}
<强>测试强>
public static IEnumerable<object[]> GetClients()
{
yield return new object[] { new TestClientBuilder("clientType1") };
yield return new object[] { new TestClientBuilder("clientType2") };
}
[Theory]
[MemberData(nameof(GetClients))]
private void ClientTheory(TestClientBuilder clientBuilder)
{
var client = clientBuilder.Build();
// ... test here
}
我还没有注意到目标对象的注入,这有点令人讨厌,但它只需要一行代码来调用我的构建器。而且,我的测试通过(并出现两次!),所以我没有抱怨。
答案 1 :(得分:11)
MemberData可以使用返回对象[]的IEnumerable的属性或方法。 在这种情况下,您将看到每种产量的单独测试结果:
public class Tests
{
[Theory]
[MemberData("TestCases", MemberType = typeof(TestDataProvider))]
public void IsLargerTest(string testName, int a, int b)
{
Assert.True(b>a);
}
}
public class TestDataProvider
{
public static IEnumerable<object[]> TestCases()
{
yield return new object[] {"case1", 1, 2};
yield return new object[] {"case2", 2, 3};
yield return new object[] {"case3", 3, 4};
}
}
但是,只要您需要传递复杂的自定义对象,无论您有多少个测试用例,测试输出窗口都只显示一个测试。 这不是理想的行为,并且在调试哪个测试用例失败时确实非常不方便。 解决方法是创建自己的包装器,它将从IXunitSerializable派生。
public class MemberDataSerializer<T> : IXunitSerializable
{
public T Object { get; private set; }
public MemberDataSerializer()
{
}
public MemberDataSerializer(T objectToSerialize)
{
Object = objectToSerialize;
}
public void Deserialize(IXunitSerializationInfo info)
{
Object = JsonConvert.DeserializeObject<T>(info.GetValue<string>("objValue"));
}
public void Serialize(IXunitSerializationInfo info)
{
var json = JsonConvert.SerializeObject(Object);
info.AddValue("objValue", json);
}
}
现在您可以将自定义对象作为Xunit Theories的参数,并在测试运行器窗口中查看/调试它们作为独立结果:
public class UnitTest1
{
[Theory]
[MemberData("TestData", MemberType = typeof(TestDataProvider))]
public void Test1(string testName, MemberDataSerializer<TestData> testCase)
{
Assert.Equal(1, testCase.Object.IntProp);
}
}
public class TestDataProvider
{
public static IEnumerable<object[]> TestData()
{
yield return new object[] { "test1", new MemberDataSerializer<TestData>(new TestData { IntProp = 1, StringProp = "hello" }) };
yield return new object[] { "test2", new MemberDataSerializer<TestData>(new TestData { IntProp = 2, StringProp = "Myro" }) };
}
}
public class TestData
{
public int IntProp { get; set; }
public string StringProp { get; set; }
}
希望这有帮助。
答案 2 :(得分:3)
在我最近的项目中,我遇到了同样的问题,经过一些研究后,我提出的解决方案如下:
实现您的自定义MyTheoryAttribute扩展FactAttribute以及实现IXunitTestCaseDiscoverer的MyTheoryDiscoverer和几个自定义MyTestCases,扩展TestMethodTestCase并根据您的喜好实现IXunitTestCase。您的自定义测试用例应该被MyTheoryDiscoverer识别并用于将您枚举的理论测试用例封装到Xunit框架可见的形式,即使传递的值不是由Xunit本地序列化并且不实现IXunitSerializable。
最重要的是什么无需更改您测试中的宝贵代码!
这是一项工作要做,但因为它已经由我完成并且可以在麻省理工学院许可下使用,随时可以使用它。它是DjvuNet项目的一部分,该项目托管在GitHub上。
使用Xunit支持代码直接链接到相关文件夹如下:
要使用它,请使用此文件创建单独的程序集,或将它们直接包含在测试项目中。
用法与Xunit TheoryAttribute完全相同,支持ClassDataAttribute和MemberDataAttribute 即:
[DjvuTheory]
[ClassData(typeof(DjvuJsonDataSource))]
public void InfoChunk_Theory(DjvuJsonDocument doc, int index)
{
// Test code goes here
}
[DjvuTheory]
[MemberData(nameof(BG44TestData))]
public void ProgressiveDecodeBackground_Theory(BG44DataJson data, long length)
{
// Test code goes here
}
信用也适用于另一位开发者,但不幸的是我无法在github上找到他的回购
答案 3 :(得分:1)
目前,当您的自定义类覆盖ToString()
时,ReSharper可以显示带有自定义参数的所有MemberData测试。
例如:
public static TheoryData<Permission, Permission, Permission> GetAddRuleData()
{
var data = new TheoryData<Permission, Permission, Permission>
{
{
new Permission("book", new[] {"read"}, null),
new Permission("book", new[] {"delete"}, new[] {"2333"}),
new Permission("book", new[] {"delete", "read"}, new[] {"*", "2333"})
},
{
new Permission("book", new[] {"read"}, null),
new Permission("music", new[] {"read"}, new[] {"2333"}), new Permission
{
Resources = new Dictionary<string, ResourceRule>
{
["book"] = new ResourceRule("book", new[] {"read"}, null),
["music"] = new ResourceRule("music", new[] {"read"}, new[] {"2333"}),
}
}
}
};
return data;
}
Permission
会覆盖ToString()
,然后在ReSharper Test Session Explorer中:
答案 4 :(得分:1)
一个简单的替代方法是,如果您使用的是.net核心项目,则可以使用“ dotnet test”在命令行中运行测试,而不是使用vstest Explorer。
结果是:
对于失败的成员数据测试,您将获得每个失败的成员数据测试的关联参数值