AutoDataAttribute的具体对象创建逻辑调用所有属性getter一次

时间:2017-11-26 07:58:46

标签: c# unit-testing xunit.net autofixture

使用AutoFixture 3.50和xUnit.NET,似乎 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="8dp" android:paddingRight="8dp"> <ListView android:id="@android:id/list" //Important this one. android:layout_width="match_parent" android:layout_height="match_parent" android:background="#00FF00" android:layout_weight="1" android:drawSelectorOnTop="false"/> </LinearLayout> 创建具体对象的方式与AutoData Theory测试创建具体对象的方式之间存在差异。

简单示例:

Fixture.Create()

使用public class Foo { private string prop; public string Prop { get { if (prop == null) { prop = "Prop"; } // Breakpoint 'A' return prop; } } } 进行测试:

Fixture

使用[Fact] public void FixtureTest() { var fixture = new Fixture(); var result = fixture.Create<Foo>(); // Breakpoint 'B1' } 进行测试:

AutoDataAttribute

在前一个测试中,断点'B1'被击中,而断点'A'从未被击中。在后一个测试中,断点'A'在断点'B2'被击中之前被击中。当我的“懒惰”初始化属性与上面的属性不同时,这是有问题的 - 因为在运行测试之前初始化了属性的支持字段,我无法测试初始化​​逻辑。

有没有办法自定义[Theory, AutoData] public void AutoDataTest(Foo sut) { var bar = 1; // Essential no-op, Breakpoint 'B2' } 以便我可以绕过这种行为?或者可能,这可能是一个错误?

1 个答案:

答案 0 :(得分:4)

事实上,这不是一个AutoFixture问题,而是一个xUnit.net问题,如果你愿意的话。您可以完全重现它,不需要像这样的AutoFixture:

[Theory, ClassData(typeof(FooTestCases))]
public void ClassDataTest(Foo sut)
{
    var bar = 1; // Essential no-op, Breakpoint 'B3'
}

private class FooTestCases : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { new Foo() };
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

如果你调试到这个ClassDataTest,你也会在遇到断点'B3'之前点击断点'A'。

原因是xUnit.net测试运行器希望为每个参数化测试提供一个好的显示名称,因此它尝试创建一个可读的字符串表示形式,传递给{{1}中每个测试用例的所有参数}。

当数据对象未显式覆盖[Theory]时,xUnit.net将回退到读取所有属性并从中构建显示字符串。这就是这里发生的事情。

我已经尝试搜索该行为的一些官方文档,但我能找到的最好的是this。正如那里建议的那样,你可以通过覆盖ToString

来解决这个问题
ToString

此更改会在使用public class Foo { private string prop; public string Prop { get { if (prop == null) { prop = "Prop"; } // Breakpoint 'A' return prop; } } public override string ToString() { return "Foo"; } } ClassData时阻止断点'A'被击中。是否要覆盖AutoData是另一个问题。

如果您不想覆盖ToString上的ToString,也许您可​​以通过测试覆盖Foo的特定于测试的子类来解决此问题,如下所示:

ToString

这也可以阻止断点'A'被击中。

只要[Theory, AutoData] public void AutoDataTestFoo(TestFoo sut) { var bar = 1; // Essential no-op, Breakpoint 'B4' } public class TestFoo : Foo { public override string ToString() { return "Foo"; } } 不是Foo,您就应该能够做到这一点。如果sealedFoo,我无法想到任何其他解决方法,而不是以sealed的方式编写测试。