Статические данные из функции xunit MemberData вычисляются дважды - PullRequest
0 голосов
/ 30 октября 2018

У меня возникли проблемы с вычислением данных из статического класса в тесте 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 голосов
/ 31 октября 2018

Во время обнаружения тестов бегунок консоли 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 будет успешным, потому что «генерация данных» и тестовый запуск будут запущены на одном процессе

...