自动混合:创建值类型时出现问题

时间:2016-06-20 14:29:17

标签: autofixture

使用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();
    }
}

1 个答案:

答案 0 :(得分:2)

我认为,您所看到的是与.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控制台运行程序运行此程序时,您可以很好地重现该问题:

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

在上面的例子中,您会注意到我已经明确使用-parallel all调用了这位选手,但我没有必要这样做,因为它是默认值。

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

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

我认为发生的是,由于并行性,Test1Test2都是并行执行的,并且基本上在相同的时间内执行。

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

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只能并行运行不同的测试

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

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

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

请注意,除一次测试运行外,所有测试都具有相同的数字。