【问题标题】:MemberData tests show up as one test instead of manyMemberData 测试显示为一个测试而不是多个
【发布时间】:2015-06-01 13:05:53
【问题描述】:

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

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

【问题讨论】:

  • IIRC 有一个 github 问题涵盖了这个和/或它已经被覆盖了
  • 你还记得是哪一期吗?我找不到描述此问题的问题。
  • 抱歉,找不到任何明确匹配的内容。也许阅读 this one 可能会帮助您将您正在寻找的问题描述为 GitHub 上的问题(例如发现、预枚举理论、测试方法与测试用例)。我还会查看文档以确定 v2 和 v1 反向兼容的预期行为(即,如果需要为每个测试用例增加/减少资源,那么预枚举理论是一把双刃剑)
  • 好的,谢谢。我提交了一个新问题。我们会看看他们的想法:)

标签: c# xunit.net data-driven-tests xunit2


【解决方案1】:

我花了很多时间试图在我的项目中解决这个问题。 @NPadrutt 本人的 This related Github discussion 提供了很多帮助,但仍然令人困惑。

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
}

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

就我而言,我不想将测试问题渗入我的主代码中。我以为我可以围绕我的真实课程编写一个瘦代理,这会欺骗 xUnit 跑步者认为它可以序列化它,但是在与它战斗的时间比我愿意承认的要长之后,我意识到我不理解的部分是:

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

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


解决方案

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

  • 1234563这是上面的代码,在我开始工作后:

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
}

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

【讨论】:

  • 确实有点烦人。有什么更好的方法更新吗?
  • @Strake 不是我这边。这是我一直在使用的解决方案,除了丑陋之外,它确实有效。
  • 至少在 vs2019 中,这会导致测试资源管理器无限加载,并锁定构建文件,因此在重新启动 VS 之前无法重建。测试输出没有特别显示任何内容。在vs2017中不是问题。想知道为什么...
  • 我正在尝试将此代码与 Selenium 一起使用进行自动化测试。我不知道如何使用“By”类型的属性作为参数而不是字符串。有任何想法吗?通过 select_project_search_bar { 获取;放; } = By.XPath("//*[@id=\"ctl00_body\"]/span/span/span[1]/input");
  • @agleno 听起来很具体,可能是它自己的问题。仅从您的评论中,我无法确定发生了什么。
【解决方案2】:

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

希望这会有所帮助。

【讨论】:

  • 序列化程序工作得很好。我稍微修改了它,使其也具有Description 属性并在ToString() 中使用它。这样,您可以(例如)"test1""test2" 显示在测试资源管理器中。
  • 我有点不高兴。我在 xUnit 存储库中为此创建了一个问题,这就是我得到的:github.com/xunit/xunit/issues/1679#issuecomment-375929120.
  • 我最终得到了与您相同的实现结果,但是在编写我的自定义 JSON 属性时。很遗憾,我们的 IXunitSerializable 中没有最终数据的访问器。
  • 我正在尝试将此代码与 Selenium 一起使用进行自动化测试。我不知道如何使用“By”类型的属性作为参数而不是字符串。有任何想法吗?通过 select_project_search_bar { 获取;放; } = By.XPath("//*[@id=\"ctl00_body\"]/span/span/span[1]/input");
【解决方案3】:

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

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

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

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

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

DjvuNet test support code

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

用法与 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 上找不到他的 repo

【讨论】:

  • 我试过了,效果很好!很遗憾,官方 xUnit 中没有包含此代码。非常感谢!你知道这段代码是否作为 Nuget 包上传到某个地方?
  • 不是 Nuget 但源代码是的,代码是 DjvuNet 项目的一部分(MIT 许可证)。 github.com/DjvuNet/DjvuNet/tree/master/DjvuNet.Shared.Tests/…
  • 我有点不高兴。我在 xUnit 存储库中创建了一个问题,这就是我得到的:github.com/xunit/xunit/issues/1679#issuecomment-375929120
  • 我正在尝试将此代码与 Selenium 一起使用进行自动化测试。我不知道如何使用“By”类型的属性作为参数而不是字符串。 DJVU 会使用它吗?通过 select_project_search_bar { 获取;放; } = By.XPath("//*[@id=\"ctl00_body\"]/span/span/span[1]/input");
【解决方案4】:

目前,当您的自定义类覆盖 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 测试会话资源管理器中:

【讨论】:

  • 是的,我也看到了。除了不是每个人都使用 R#。至少,当它在控制台中运行时,它会给出正确的结果(测试数量和所有)。
【解决方案5】:

一个简单的替代方法是,如果您使用的是 .net 核心项目,而不是使用 vstest 资源管理器,您可以使用“dotnet test”在命令行中运行测试

结果是:

  • 获取测试总数
  • 通过的金额
  • 失败的金额

对于失败的成员数据测试,您将获得每个失败的成员数据测试的关联参数值

【讨论】:

    猜你喜欢
    • 2021-01-17
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 2011-04-14
    • 1970-01-01
    • 2023-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多