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

Static Data from xunit MemberData function is computed twice

我在计算两次 C# Xunit 测试中来自静态 class 的计算数据时遇到一些问题。

这将用于的实际生产代码要复杂得多,但后面的代码足以说明我所看到的问题。

在下面的代码中,我有一个随机生成的、延迟加载的 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}");
    }
}

在发现测试时,Visual Studio Xunit 控制台 运行ner 使用所有属性(如 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 将会成功,因为 'data generation' 和测试 运行 将在一个进程上启动