来自xunit MemberData函数的静态数据被计算两次

时间:2018-10-30 19:12:21

标签: c# xunit xunit.net

在对C#Xunit测试中的静态类的计算数据进行两次计算时遇到麻烦。

将要使用的实际生产代码要复杂得多,但是下面的代码足以显​​示我所看到的问题。

在下面的代码中,我从当前时间播种了一个随机生成的,延迟加载的int

我在这里测试的只是此属性与其自身相等。我通过MemberData函数将属​​性的值插入测试。

由于该属性只应初始化一次,所以我希望该测试始终可以通过。我希望在运行RandomIntMemberData函数时不会初始化静态字段。

但是,此测试始终失败。插入到测试中的值以及根据其测试的值总是不同的。

此外,如果我进行调试,我只会看到初始化代码被击中一次。即,正在测试的值。我从没看到要测试的值的初始化。

是我误解了什么,还是Xunit在后台做了一些奇怪的魔术来设置其输入数据,然后在实际运行测试时再次初始化该值?

再现错误的最小代码

public static class TestRandoIntStaticClass
{
    private static readonly Lazy<int> LazyRandomInt = new Lazy<int>(() =>
    {
        // lazily initialize a random interger seeded off of the current time
        // according to readings, this should happen only once
        return new Random((int) DateTime.Now.Ticks).Next();
    });

    // according to readings, this should be a thread safe operation
    public static int RandomInt => LazyRandomInt.Value; 
}

测试

public class TestClass
{
    public static IEnumerable<object[]> RandomIntMemberData()
    {
        var randomInt = new List<object[]>
        {
            new object[] {TestRandoIntStaticClass.RandomInt},
        };

        return randomInt as IEnumerable<object[]>;
    }

    [Theory]
    [MemberData(nameof(RandomIntMemberData))]
    public void RandoTest(int rando)
    {
        // these two ought to be equal if TestRandoIntStaticClass.RandomInt is only initialized once 
        Assert.True(rando == TestRandoIntStaticClass.RandomInt,
                    $"{nameof(rando)} = {rando} but {nameof(TestRandoIntStaticClass.RandomInt)} = {TestRandoIntStaticClass.RandomInt}");
    }
}

1 个答案:

答案 0 :(得分:4)

在发现测试时,Visual Studio Xunit控制台运行程序为所有属性(例如MemberData,ClassData,DataAttribute)创建带有测试数据的AppDomain,因此所有数据仅在构建后保存在内存中(这也是XUnit要求类可序列化的原因) )。

我们可以通过在您的方法中添加一个简单的记录器来验证这一点:

namespace XUnitTestProject1
{
    public class TestClass
    {
        public static IEnumerable<object[]> RandomIntMemberData()
        {
            var randomInt = new List<object[]>
            {
                new object[]
                    {TestRandoIntStaticClass.RandomInt},
            };
            return randomInt;
        }

        [Theory]
        [MemberData(nameof(RandomIntMemberData))]
        public void RandoTest(int rando)
        {
            // these two ought to be equal if TestRandoIntStaticClass.RandomInt is only initialized once 
            Assert.True(rando == TestRandoIntStaticClass.RandomInt, $"{nameof(rando)} = {rando} but {nameof(TestRandoIntStaticClass.RandomInt)} = {TestRandoIntStaticClass.RandomInt}");
        }

    }

    public static class TestRandoIntStaticClass
    {
        private static readonly Lazy<int> LazyRandomInt = new Lazy<int>(() =>
        {   // lazily initialize a random interger seeded off of the current time
            // according to readings, this should happen only once
            var randomValue = new Random((int) DateTime.Now.Ticks).Next();

            File.AppendAllText(@"D:\var\log.txt", $"Call TestRandoIntStaticClass {randomValue}; ThreadId {Thread.CurrentThread.ManagedThreadId} " + Environment.NewLine);
            return randomValue;
        });

        public static int RandomInt => LazyRandomInt.Value; // according to readings, this should be a thread safe operation
    }
}

结果,我们在日志中看到了

> Call TestRandoIntStaticClass 1846311153; ThreadId 11  
> Call TestRandoIntStaticClass 1007825738; ThreadId 14

在测试执行结果中

rando = 1846311153 but RandomInt = 1007825738
Expected: True
Actual:   False
   at 

但是,如果您使用dotnet test将会成功,因为“数据生成”和测试运行将在一个进程中启动