Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

. tl; dr: [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, в тесте Explorer появился только один элемент, а не два. Это связано с тем, что предоставленный объект 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}";
    }
}

Test

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
}

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

Ответ 2

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; }
}

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

Ответ 3

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

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

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

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

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

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

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

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

[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