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

标签 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}");
    }
}

最佳答案

在测试发现时,Visual Studio Xunit 控制台运行器创建 AppDomain,其中包含所有属性(如 MemberData、ClassData、DataAttribute)的测试数据,因此所有数据在构建后仅保存在内存中(这也是 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 将会成功,因为“数据生成”和测试运行将在一个进程上启动

关于c# - 来自 xunit MemberData 函数的静态数据被计算两次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53071355/

相关文章:

c# - 小 cucumber 功能无法将任何方法与步骤匹配

tfs - 调用执行程序时发生异常 'executor://xunit/VsTestRunner2/uap' : Could not load file or assembly 'System. IO.FileSystem

c# - Youtube API 无效搜索查询 invalidSearchFilter 错误

c# - 虚拟键盘(类似 Swype 的键盘)Windows 窗体应用程序 C#

c# - SQLite 内存数据库使用时态表测试 EF Core 应用程序

unit-testing - dotnet 核心的无约束隔离(模拟)框架

c# - Xunit 报告工具

c# - 在 Unity 中检测 applicationDidBecomeActive 何时?

c# - 如何编辑在 IdentityDbContext 中创建的表

.net-core - 为什么在 F# 任务中引用此 IDisposable 时会被释放?