Тесты MemberData отображаются как один тест вместо множества

Когда вы используете [Theory] вместе с [InlineData], он создаст тест для каждого элемента предоставленных встроенных данных. Однако, если вы используете [MemberData], он будет отображаться как один тест.

Есть ли способ сделать так, чтобы тесты [MemberData] отображались как несколько тестов?


person NPadrutt    schedule 01.06.2015    source источник
comment
IIRC есть проблема github, посвященная этому, и / или она была освещена там   -  person Ruben Bartelink    schedule 01.06.2015
comment
Du вы помните, какой вопрос это было? Я не смог найти проблему, которая описывает это.   -  person NPadrutt    schedule 02.06.2015
comment
Извините, не удалось найти ничего, что точно соответствует. Возможно, чтение этого может помочь вам сформулировать то, что вы ищете, как проблему. на GitHub (например, обнаружение, предварительное перечисление теорий, метод тестирования и тестовый пример). Я бы также заглянул в документы, чтобы определить, каково предполагаемое поведение для обратной совместимости v2 и v1 (т. е. предварительное перечисление теорий — это обоюдоострый меч, если нужно раскручивать ресурсы вверх/вниз для каждого тестового примера)   -  person Ruben Bartelink    schedule 02.06.2015
comment
Хорошо, спасибо. Я представил новый выпуск. Посмотрим, что они думают :)   -  person NPadrutt    schedule 02.06.2015


Ответы (5)


Я потратил много времени, пытаясь понять это в своем проекте. Это связанное обсуждение Github от самого @NPadrutt очень помогло, но все еще сбивало с толку.

Суть в следующем: [MemberInfo] сообщит об одном групповом тесте, если предоставленные объекты для каждого теста не могут быть полностью сериализованы и десериализованы путем реализации IXunitSerializable.


Фон

Моя собственная тестовая установка была примерно такой:

public static IEnumerable<object[]> GetClients()
{
    yield return new object[] { new Impl.Client("clientType1") };
    yield return new object[] { new Impl.Client("clientType2") };
}

[Theory]
[MemberData(nameof(GetClients))]
public void ClientTheory(Impl.Client testClient)
{
    // ... test here
}

Тест выполнялся дважды, по одному разу для каждого объекта из [MemberData], как и ожидалось. По опыту @NPadrutt, в обозревателе тестов появился только один элемент вместо двух. Это связано с тем, что предоставленный объект Impl.Client не может быть сериализован ни одним интерфейсом, поддерживаемым xUnit (подробнее об этом позже).

В моем случае я не хотел переносить проблемы тестирования в свой основной код. Я думал, что смогу написать тонкий прокси вокруг моего реального класса, который обманет исполнителя xUnit, заставив его думать, что он может сериализовать его, но после борьбы с ним дольше, чем я хотел бы признать, я понял, что часть, которую я не понимал, была :

Объекты не просто сериализуются во время обнаружения для подсчета перестановок; каждый объект также десериализуется во время выполнения теста при запуске теста.

Таким образом, любой объект, который вы предоставляете с помощью [MemberData], должен поддерживать полную циклическую (де-)сериализацию. Сейчас это кажется мне очевидным, но я не смог найти никакой документации по этому вопросу, пока пытался понять это.


Решение

  • Убедитесь, что каждый объект (и любой не-примитивный объект, который он может содержать) может быть полностью сериализован и десериализован. Реализация xUnit IXunitSerializable сообщает xUnit, что это сериализуемый объект.

  • Если, как в моем случае, вы не хотите добавлять атрибуты в основной код, одним из решений является создание тонкого сериализуемого класса-строителя для тестирования, который может представлять все необходимое для воссоздания фактического класса. Вот приведенный выше код после того, как я заставил его работать:

TestClientBuilder

public class TestClientBuilder : IXunitSerializable
{
    private string type;

    // required for deserializer
    public TestClientBuilder()
    {
    }

    public TestClientBuilder(string type)
    {
        this.type = type;
    }

    public Impl.Client Build()
    {
        return new Impl.Client(type);
    }

    public void Deserialize(IXunitSerializationInfo info)
    {
        type = info.GetValue<string>("type");
    }

    public void Serialize(IXunitSerializationInfo info)
    {
        info.AddValue("type", type, typeof(string));
    }

    public override string ToString()
    {
        return $"Type = {type}";
    }
}

Тест

public static IEnumerable<object[]> GetClients()
{
    yield return new object[] { new TestClientBuilder("clientType1") };
    yield return new object[] { new TestClientBuilder("clientType2") };
}

[Theory]
[MemberData(nameof(GetClients))]
private void ClientTheory(TestClientBuilder clientBuilder)
{
    var client = clientBuilder.Build();
    // ... test here
}

Немного раздражает, что я больше не получаю целевой объект, но это всего лишь одна дополнительная строка кода для вызова моего компоновщика. И мои тесты проходят (и показываются дважды!), так что я не жалуюсь.

person Nate Barbettini    schedule 26.08.2015
comment
Действительно слегка раздражает. Любое обновление о лучших способах сделать это? - person Strake; 21.02.2017
comment
@Strake Не с моей стороны. Это решение, которое я использовал, и оно работает, кроме того, что оно уродливое. - person Nate Barbettini; 22.02.2017
comment
По крайней мере, в vs2019 это приводит к бесконечной загрузке обозревателя тестов и блокировке файлов сборки, поэтому пересборка невозможна до перезапуска VS. Тестовый вывод ничего особенного не показывает. Не проблема в vs2017. Интересно, почему... - person Douglas Gaskell; 03.10.2019
comment
Я пытаюсь использовать этот код с Selenium для автоматического тестирования. Я не могу понять, как использовать свойство типа By в качестве параметра вместо строки. Любые идеи? общественность По select_project_search_bar { получить; установлен; } = By.XPath(//*[@id=\ctl00_body\]/span/span/span[1]/input); - person agleno; 18.03.2021
comment
@agleno Звучит достаточно конкретно, чтобы это мог быть отдельный вопрос. Только из вашего комментария я не могу понять, что происходит. - person Nate Barbettini; 19.03.2021
comment
@NateBarbettini Я рисковал своей рукой! Я использую Selenium для автоматического тестирования, и у меня есть класс, который хранит Xpaths элементов на веб-странице. Я пытаюсь использовать Xunit для параметризации некоторых тестов, например, я тестирую некоторую навигацию, поэтому это один и тот же тест для каждой страницы, я просто передаю xpath и ожидаемый заголовок страницы и утверждаю, правильно это или нет. Проблема с Xunit/Visual Studio заключается в том, что он не обнаруживает каждый тест по отдельности. Примеры кода здесь работают, но только для строк. Мне нужно каким-то образом заставить Xunit обнаружить мои параметризованные тесты. Я спрашивал в другом месте........ - person agleno; 19.03.2021
comment
@NateBarbettini Кажется, я не могу сериализовать тип данных By, используемый в селене, так же, как вы, ребята, использовали здесь строку. Я спрашивал о переполнении SO раньше и даже безуспешно связывался с создателями Xunit! Эта страница наиболее близка к тому, что я когда-либо видел, чтобы делать именно то, что мне нужно. - person agleno; 19.03.2021
comment
например stackoverflow.com/questions/66138629/ - person agleno; 19.03.2021

MemberData может работать со свойствами или методами, которые возвращают IEnumerable объекта[]. Вы увидите отдельный результат теста для каждого выхода в этом сценарии:

public class Tests
{ 
    [Theory]
    [MemberData("TestCases", MemberType = typeof(TestDataProvider))]
    public void IsLargerTest(string testName, int a, int b)
    {
        Assert.True(b>a);
    }
}

public class TestDataProvider
{
    public static IEnumerable<object[]> TestCases()
    {
        yield return new object[] {"case1", 1, 2};
        yield return new object[] {"case2", 2, 3};
        yield return new object[] {"case3", 3, 4};
    }
}

Однако, как только вам нужно будет пройти сложные пользовательские объекты, независимо от того, сколько у вас будет тестовых случаев, в окне вывода теста будет отображаться только один тест. Это не идеальное поведение и действительно очень неудобно при отладке того тестового случая, в котором произошел сбой. Обходной путь — создать собственную оболочку, которая будет производной от IXunitSerializable.

public class MemberDataSerializer<T> : IXunitSerializable
    {
        public T Object { get; private set; }

        public MemberDataSerializer()
        {
        }

        public MemberDataSerializer(T objectToSerialize)
        {
            Object = objectToSerialize;
        }

        public void Deserialize(IXunitSerializationInfo info)
        {
            Object = JsonConvert.DeserializeObject<T>(info.GetValue<string>("objValue"));
        }

        public void Serialize(IXunitSerializationInfo info)
        {
            var json = JsonConvert.SerializeObject(Object);
            info.AddValue("objValue", json);
        }
    }

Теперь вы можете использовать свои пользовательские объекты в качестве параметров для Xunit Theories и по-прежнему просматривать/отлаживать их как независимые результаты в окне запуска тестов:

public class UnitTest1
{
    [Theory]
    [MemberData("TestData", MemberType = typeof(TestDataProvider))]
    public void Test1(string testName, MemberDataSerializer<TestData> testCase)
    {
        Assert.Equal(1, testCase.Object.IntProp);
    }
}

public class TestDataProvider
{
    public static IEnumerable<object[]> TestData()
    {
        yield return new object[] { "test1", new MemberDataSerializer<TestData>(new TestData { IntProp = 1, StringProp = "hello" }) };
        yield return new object[] { "test2", new MemberDataSerializer<TestData>(new TestData { IntProp = 2, StringProp = "Myro" }) };      
    }
}

public class TestData
{
    public int IntProp { get; set; }
    public string StringProp { get; set; }
}

Надеюсь это поможет.

person user1807319    schedule 28.03.2017
comment
Сериализатор работает отлично. Я немного изменил его, чтобы он также имел свойство Description и использовал его в ToString(). Таким образом, вы можете (например) "test1" и "test2" отображаться в обозревателе тестов. - person user247702; 15.09.2017
comment
Я немного расстроен. Я создал задачу для этого в репозитории xUnit, и вот что я получил: github.com/xunit/xunit/issues/1679#issuecomment-375929120. - person SuperJMN; 26.03.2018
comment
В итоге я получил тот же результат реализации, что и вы, но при написании собственного атрибута JSON. Жаль, что у нас нет аксессора для финальных данных в нашем IXunitSerializable. - person Nordes; 27.08.2018
comment
Я пытаюсь использовать этот код с Selenium для автоматического тестирования. Я не могу понять, как использовать свойство типа By в качестве параметра вместо строки. Любые идеи? общественность По select_project_search_bar { получить; установлен; } = By.XPath(//*[@id=\ctl00_body\]/span/span/span[1]/input); - person agleno; 18.03.2021

В моем недавнем проекте я столкнулся с той же проблемой, и после некоторых исследований решение, которое я придумал, выглядит следующим образом:

Реализуйте свой собственный MyTheoryAttribute, расширяющий FactAttribute, вместе с MyTheoryDiscoverer, реализующим IXunitTestCaseDiscoverer, и несколько пользовательских MyTestCase, расширяющих TestMethodTestCase и реализующих IXunitTestCase по своему вкусу. Ваши пользовательские тестовые примеры должны распознаваться MyTheoryDiscoverer и использоваться для инкапсуляции ваших пронумерованных тестовых случаев теории в форме, видимой для платформы Xunit, даже если переданные значения не сериализуются изначально Xunit и не реализуют IXunitSerializable.

Что наиболее важно, нет необходимости изменять ваш драгоценный тестируемый код!

Это немного работы, но так как это уже было сделано мной и доступно по лицензии MIT, не стесняйтесь использовать его. Это часть проекта DjvuNet, размещенного на GitHub.

Прямая ссылка на соответствующую папку с кодом поддержки Xunit приведена ниже:

Код поддержки тестирования DjvuNet

Чтобы использовать его, либо создайте отдельную сборку с этими файлами, либо включите их непосредственно в свой тестовый проект.

Использование точно такое же, как и с Xunit TheoryAttribute, и поддерживаются как ClassDataAttribute, так и MemberDataAttribute, т. е.:

[DjvuTheory]
[ClassData(typeof(DjvuJsonDataSource))]
public void InfoChunk_Theory(DjvuJsonDocument doc, int index)
{
    // Test code goes here
}


[DjvuTheory]
[MemberData(nameof(BG44TestData))]
public void ProgressiveDecodeBackground_Theory(BG44DataJson data, long length)
{
    // Test code goes here
}

Кредит также принадлежит другому разработчику, но, к сожалению, я не могу найти его репозиторий на github.

person Jacek Blaszczynski    schedule 14.05.2017
comment
Я попробовал это, и это работает! Жаль, что этот код не включен в официальный xUnit. Большое спасибо! Знаете ли вы, загружается ли этот фрагмент кода куда-либо в виде пакета Nuget? - person SuperJMN; 24.03.2018
comment
Не Nuget, а источник да, код является частью проекта DjvuNet (лицензия MIT). github.com/DjvuNet/DjvuNet/tree/master/DjvuNet. Общие.Тесты/ - person Jacek Blaszczynski; 25.03.2018
comment
Я немного расстроен. Я создал задачу в репозитории xUnit и вот что получил: github.com /xunit/xunit/issues/1679#issuecomment-375929120 - person SuperJMN; 26.03.2018
comment
Я пытаюсь использовать этот код с Selenium для автоматического тестирования. Я не могу понять, как использовать свойство типа By в качестве параметра вместо строки. Будет ли DJVU работать с этим? общественность По select_project_search_bar { получить; установлен; } = By.XPath(//*[@id=\ctl00_body\]/span/span/span[1]/input); - person agleno; 18.03.2021

На данный момент ReSharper может отображать все тесты MemberData с пользовательскими параметрами, когда ваши пользовательские классы переопределяют ToString().

Например :

public static TheoryData<Permission, Permission, Permission> GetAddRuleData()
{
    var data = new TheoryData<Permission, Permission, Permission>
    {
        {
            new Permission("book", new[] {"read"}, null),
            new Permission("book", new[] {"delete"}, new[] {"2333"}),
            new Permission("book", new[] {"delete", "read"}, new[] {"*", "2333"})
        },
        {
            new Permission("book", new[] {"read"}, null),
            new Permission("music", new[] {"read"}, new[] {"2333"}), new Permission
            {
                Resources = new Dictionary<string, ResourceRule>
                {
                    ["book"] = new ResourceRule("book", new[] {"read"}, null),
                    ["music"] = new ResourceRule("music", new[] {"read"}, new[] {"2333"}),
                }
            }
        }
    };
    return data;
}

Permission переопределяет ToString(), затем в ReSharper Test Session Explorer:

xunitR#

person Tao Zhu    schedule 18.07.2018
comment
да, я тоже это видел. За исключением того, что не все, кто использует R#. По крайней мере, когда он запускается в консоли, он дает правильные результаты (количество тестов и все такое). - person Nordes; 27.08.2018

Простая альтернатива заключается в том, что если вы используете основной проект .net, вместо использования vstest explorer вы можете запускать свои тесты в командной строке, используя тест dotnet.

Результаты:

  • получить общее количество тестов
  • сумма, прошедшая
  • сумма, которая не удалась

Для неудачных тестов данных элементов вы получите соответствующие значения параметров для каждого из неудавшихся тестов данных элементов.

person Hitman Dev    schedule 16.09.2020