MemberData测试显示为一项测试,而不是多项测试 [英] MemberData tests show up as one test instead of many

查看:116
本文介绍了MemberData测试显示为一项测试,而不是多项测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当您将 [Theory] ​​ [InlineData] 一起使用时,它将为每个内联项创建一个测试提供的数据。但是,如果使用 [MemberData] ,它将仅显示为一项测试。



有没有办法使 [MemberData] 测试显示为多个测试?

解决方案

I花了很多时间试图在我的项目中弄清楚这一点。 来自@NPadrutt本人的与Github相关的讨论起到了很大作用,但仍然令人困惑。 / p>

tl; dr是: [MemberInfo] 将报告单个组测试,除非每个测试都提供了对象可以通过实现 IXunitSerializable 来完全序列化和反序列化






背景



我自己的测试设置如下:

 公共静态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)
{
// ...在这里测试
}

测试进行了两次,每次运行一次按预期,来自 [MemberData] 的对象。正如@NPadrutt所经历的,测试浏览器中仅显示一项,而不是两项。这是因为提供的对象 Impl.Client 不能由xUnit支持的任何一个接口序列化(稍后会详细介绍)。



就我而言,我不想将测试问题放到我的主代码中。我以为我可以在我的真实类上写一个瘦代理,这会使xUnit运行程序愚弄它可以序列化,但是在与它争夺了我不愿承认的时间之后,我意识到我不了解的部分是:


对象不只是在发现期间进行序列化以计算排列;在测试开始时,每个对象也会在测试运行时反序列化


因此,您提供的任何对象 [MemberData] 必须支持完整的往返(反)序列化。现在对我来说这似乎很明显,但是在尝试弄清楚它时我找不到任何文档。






解决方案




  • 确保每个对象(及其可能包含的任何非原始对象)都可以完全序列化和反序列化。实现xUnit的 IXunitSerializable 会告诉xUnit它是一个可序列化的对象。


  • 不想在主代码中添加属性,一种解决方案是制作一个瘦的可序列化的构建器类进行测试,该类可以表示重新创建实际类所需的所有内容。这是上面的代码,在我可以使用后:




TestClientBuilder

 公共类TestClientBuilder:IXunitSerializable 
{
私有字符串类型;

//解串器所需的
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));
}

公共重写字符串ToString()
{
return $ Type = {type};
}
}

测试

 公共静态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客户端= clientBuilder.Build();
// ...在这里测试
}

我有点讨厌不要再注入目标对象了,但这只是调用我的构建器的另一行代码。而且,我的测试通过了(并显示两次!),所以我没有抱怨。


When you use [Theory] together with [InlineData] it will create a test for each item of inline data that is provided. However, if you use [MemberData] it will just show up as one test.

Is there a way to make [MemberData] tests show up as multiple tests?

解决方案

I spent a lot of time trying to figure this one out in my project. This related Github discussion from @NPadrutt himself helped a lot, but it was still confusing.

The tl;dr is this: [MemberInfo] will report a single group test unless the provided objects for each test can be completely serialized and deserialized by implementing IXunitSerializable.


Background

My own test setup was something like:

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
}

The test ran twice, once for each object from [MemberData], as expected. As @NPadrutt experienced, only one item showed up in the Test Explorer, instead of two. This is because the provided object Impl.Client was not serializable by either interface xUnit supports (more on this later).

In my case, I didn't want to bleed test concerns into my main code. I thought I could write a thin proxy around my real class that would fool the xUnit runner into thinking it could serialize it, but after fighting with it for longer than I'd care to admit, I realized the part I wasn't understanding was:

The objects aren't just serialized during discovery to count permutations; each object is also deserialized at test run time as the test starts.

So any object you provide with [MemberData] must support a full round-trip (de-)serialization. This seems obvious to me now, but I couldn't find any documentation on it while I was trying to figure it out.


Solution

  • Make sure every object (and any non-primitive it may contain) can be fully serialized and deserialized. Implementing xUnit's IXunitSerializable tells xUnit that it's a serializable object.

  • If, as in my case, you don't want to add attributes to the main code, one solution is to make a thin serializable builder class for testing that can represent everything needed to recreate the actual class. Here's the above code, after I got it to work:

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}";
    }
}

Test

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
}

It's mildly annoying that I don't get the target object injected anymore, but it's just one extra line of code to invoke my builder. And, my tests pass (and show up twice!), so I'm not complaining.

这篇关于MemberData测试显示为一项测试,而不是多项测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆