Autofixture:创建值类型时出现问题

Autofixture: Issue when creating value types

使用 xUnit.net 调试以下代码的测试方法 Test1Test2 并放置一个断点和 CreateValueAndReferenceType() 的结尾,您会看到变量 valueType 在两次运行中都相同,而变量 referenceType 已更改。前者对我来说是令人惊讶的,也是一个问题(我添加带有字符串类型的行只是为了完整性)。

public class MyFixture : Fixture
{
    public void CreateValueAndReferenceType()
    {
        var valueType = this.Create<int>();
        var referenceTye = this.Create<string>();
    }
}

public class TestClass1
{
    [Fact]
    public void Test1()
    {
        var myFixture = new MyFixture();
        myFixture.CreateValueAndReferenceType();
    }
}

public class TestClass2
{
    [Fact]
    public void Test2()
    {
        var myFixture = new MyFixture();
        myFixture.CreateValueAndReferenceType();
    }
}

我认为您看到的是与 .NET 中的伪随机数生成相关的基本问题(IIRC,其他平台也有类似问题)。本质上,System.Random 是确定性的,但使用随机种子初始化,除其他外,取决于计算机的当前时间。如果您在紧密循环中创建 Random 的实例,代码的执行速度将超过系统时钟的精度。像这样:

for (int i = 0; i < 10; i++)
    Console.Write(new Random().Next(0, 9));

通常会产生这样的输出:

5555555555

AutoFixture 中的大多数值由各种 Random 实例生成 - string 类型除外,其中值由 Guid.NewGuid().ToString().

生成

认为 你看到这个的原因是 xUnit.net 的并行执行。

为了查明问题,我改写了问题,使其不依赖于调试或继承:

public static class Reporter
{
    public static void CreateValueAndReferenceType(
        IFixture fixture,
        ITestOutputHelper @out)
    {
        var valueType = fixture.Create<int>();
        var referenceTye = fixture.Create<string>();

        @out.WriteLine("valueType: {0}", valueType);
        @out.WriteLine("referenceType: {0}", referenceTye);
    }
}

public class TestClass1
{
    private readonly ITestOutputHelper @out;

    public TestClass1(ITestOutputHelper @out)
    {
        this.@out = @out;
    }

    [Fact]
    public void Test1()
    {
        Reporter.CreateValueAndReferenceType(new Fixture(), this.@out);
    }
}

public class TestClass2
{
    private readonly ITestOutputHelper @out;

    public TestClass2(ITestOutputHelper @out)
    {
        this.@out = @out;
    }

    [Fact]
    public void Test2()
    {
        Reporter.CreateValueAndReferenceType(new Fixture(), this.@out);
    }
}

当您 运行 使用 xUnit.net 控制台 运行ner 时,您可以看到很好地重现了这个问题:

$ packages/xunit.runner.console.2.1.0/tools/xunit.console 37925109/bin/Debug/Ploeh.Whosebug.Q37925109.dll -diagnostics
-parallel all
xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
  Discovering: Ploeh.Whosebug.Q37925109 (app domain = on [shadow copy], method display = ClassAndMethod)
  Discovered:  Ploeh.Whosebug.Q37925109 (running 2 test cases)
  Starting:    Ploeh.Whosebug.Q37925109 (parallel test collections = on, max threads = 4)
    Ploeh.Whosebug.Q37925109.TestClass2.Test2 [PASS]
      Output:
        valueType: 246
        referenceType: cc39f570-046a-4a0a-8adf-ab7deadd0e26
    Ploeh.Whosebug.Q37925109.TestClass1.Test1 [PASS]
      Output:
        valueType: 246
        referenceType: 87455351-03f7-4640-99fb-05af910da267
  Finished:    Ploeh.Whosebug.Q37925109
=== TEST EXECUTION SUMMARY ===
   Ploeh.Whosebug.Q37925109  Total: 2, Errors: 0, Failed: 0, Skipped: 0, Time: 0,429s

在上面的例子中,你会注意到我明确地调用了运行ner与-parallel all,但我没有这样做,因为它是默认设置。

另一方面,如果您使用 -parallel none 关闭并行化,您会看到值不同:

$ packages/xunit.runner.console.2.1.0/tools/xunit.console 37925109/bin/Debug/Ploeh.Whosebug.Q37925109.dll -diagnostics
-parallel none
xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
  Discovering: Ploeh.Whosebug.Q37925109 (app domain = on [shadow copy], method display = ClassAndMethod)
  Discovered:  Ploeh.Whosebug.Q37925109 (running 2 test cases)
  Starting:    Ploeh.Whosebug.Q37925109 (parallel test collections = off, max threads = 4)
    Ploeh.Whosebug.Q37925109.TestClass2.Test2 [PASS]
      Output:
        valueType: 203
        referenceType: 1bc75a33-5542-4d9f-b42d-57ed85dc418d
    Ploeh.Whosebug.Q37925109.TestClass1.Test1 [PASS]
      Output:
        valueType: 117
        referenceType: 6a508699-dc35-4bcd-8a7b-15eba64b24b4
  Finished:    Ploeh.Whosebug.Q37925109
=== TEST EXECUTION SUMMARY ===
   Ploeh.Whosebug.Q37925109  Total: 2, Errors: 0, Failed: 0, Skipped: 0, Time: 0,348s

我认为发生的事情是,由于并行性,Test1Test2 都是并行执行的,并且基本上是在同一个 tick 内。

一种解决方法是将两个测试放在同一个测试中 class:

public class TestClass1
{
    private readonly ITestOutputHelper @out;

    public TestClass1(ITestOutputHelper @out)
    {
        this.@out = @out;
    }

    [Fact]
    public void Test1()
    {
        Reporter.CreateValueAndReferenceType(new Fixture(), this.@out);
    }

    [Fact]
    public void Test2()
    {
        Reporter.CreateValueAndReferenceType(new Fixture(), this.@out);
    }
}

这会产生两个不同的整数值,因为 (IIRC) xUnit.net 只有 运行 不同的并行测试 classes:

$ packages/xunit.runner.console.2.1.0/tools/xunit.console 37925109/bin/Debug/Ploeh.Whosebug.Q37925109.dll -diagnostics
-parallel all
xUnit.net Console Runner (64-bit .NET 4.0.30319.42000)
  Discovering: Ploeh.Whosebug.Q37925109 (app domain = on [shadow copy], method display = ClassAndMethod)
  Discovered:  Ploeh.Whosebug.Q37925109 (running 2 test cases)
  Starting:    Ploeh.Whosebug.Q37925109 (parallel test collections = on, max threads = 4)
    Ploeh.Whosebug.Q37925109.TestClass1.Test2 [PASS]
      Output:
        valueType: 113
        referenceType: e8c30ad8-f2c8-4767-9e9f-69b55c50e659
    Ploeh.Whosebug.Q37925109.TestClass1.Test1 [PASS]
      Output:
        valueType: 232
        referenceType: 3eb60bf3-4d43-4a91-aef2-42f7e23e35b3
  Finished:    Ploeh.Whosebug.Q37925109
=== TEST EXECUTION SUMMARY ===
   Ploeh.Whosebug.Q37925109  Total: 2, Errors: 0, Failed: 0, Skipped: 0, Time: 0,360s

这个理论也得到了以下事实的证实:如果你重复实验足够多次,你会偶尔看到不同的数字。以下是 25 次测试 运行s:

的整数结果
33  33
92  92
211 211
13  13
9   9
160 160
55  55
155 155
137 137
161 161
242 242
183 183
237 237
151 151
104 104
254 254
123 123
244 244
144 144
223 9
196 196
126 126
199 199
221 221
132 132

请注意,除一项测试 运行 外,所有测试的编号都相同。