MemberData测试显示为一个测试而不是许多测试

NPa*_*utt 29 c# xunit.net data-driven-tests xunit2

当您使用[Theory]连同[InlineData]它会创建提供了在线数据的每个项目的测试.但是,如果您使用[MemberData]它,它将只显示为一个测试.

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

Nat*_*ini 28

我花了很多时间试图在我的项目中找到这个.这与来自@NPadrutt的Github讨论本身有很大的帮助,但它仍然令人困惑.

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


背景

我自己的测试设置是这样的:

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
}
Run Code Online (Sandbox Code Playgroud)

测试运行两次,每个对象一次[MemberData],如预期的那样.正如@NPadrutt所经历的那样,只有一个项目出现在测试资源管理器中,而不是两个.这是因为提供的对象Impl.Client不能通过xUnit支持的任何接口进行序列化(稍后将详细介绍).

就我而言,我不想将测试问题泄露到我的主代码中.我以为我可以在真正的课堂上写一个瘦代理,这会欺骗xUnit跑步者认为它可以序列化它,但是在与它战斗的时间超过我想要承认的时间后,我意识到我不理解的部分是:

这些对象不仅在发现期间被序列化以计算排列; 在测试运行时,每个对象也会在测试开始时反序列化.

因此,您提供的任何对象都[MemberData]必须支持完整的往返(de-)序列化.这对我来说似乎很明显,但在我试图找出它时,我找不到任何关于它的文档.


  • 确保每个对象(以及它可能包含的任何非基元)都可以完全序列化和反序列化.实现xUnit 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}";
    }
}
Run Code Online (Sandbox Code Playgroud)

测试

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
}
Run Code Online (Sandbox Code Playgroud)

我不再注入目标对象,这有点令人讨厌,但它只是一行额外的代码来调用我的构建器.而且,我的测试通过(并出现两次!),所以我不抱怨.

  • 至少在 vs2019 中,这会导致测试资源管理器无限加载,并锁定构建文件,因此在 VS 重新启动之前无法进行重建。测试输出没有显示任何特别的内容。vs2017 没问题。想知道为什么... (2认同)

小智 13

MemberData可以使用返回object []的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};
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,只要您需要传递复杂的自定义对象,无论您有多少测试用例,测试输出窗口都只显示一个测试.这不是理想的行为,并且在调试哪个测试用例失败时确实非常不方便.解决方法是创建自己的包装器,它将从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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

现在,您可以将自定义对象作为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; }
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.

  • 序列化器效果很好。我稍微修改了它,让它也有一个 `Description` 属性,并在 `ToString()` 中使用它。这样,您可以(例如)在测试资源管理器中显示“test1”和“test2”。 (2认同)

Jac*_*ski 5

在我最近的项目中,我遇到了同样的问题,经过一些研究,我提出的解决方案如下:

实现扩展 FactAttribute 的自定义 MyTheoryAttribute 以及实现 IXunitTestCaseDiscoverer 的 MyTheoryDiscoverer 和扩展 TestMethodTestCase 并根据您的喜好实现 IXunitTestCase 的几个自定义 MyTestCases。您的自定义测试用例应该被 MyTheoryDiscoverer 识别并用于以 Xunit 框架可见的形式封装您的枚举理论测试用例,即使传递的值不是由 Xunit 本机序列化并且没有实现 IXunitSerializable。

最重要的是无需更改您宝贵的测试代码

这是一些工作要做,但因为它已经由我完成并且在 MIT 许可下可用,请随意使用它。它是托管在 GitHub 上的DjvuNet项目的一部分。

带有 Xunit 支持代码的相关文件夹的直接链接如下:

DjvuNet 测试支持代码

要使用它,请使用此文件创建单独的程序集或将它们直接包含到您的测试项目中。

用法与 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
}
Run Code Online (Sandbox Code Playgroud)

归功于另一位开发人员,但不幸的是我在 github 上找不到他的 repo