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

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

есть ли способ сделать [MemberData] тесты показывают, как несколько тестов?

4 ответов


Я потратил много времени, пытаясь разобраться в моем проекте. это связано с обсуждением 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, только один элемент появился в Проводнике тестов вместо двух. Это потому, что предоставленный объект Impl.Client не был сериализуемым ни одним интерфейсом, который поддерживает xUnit (подробнее об этом позже).

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

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

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


решение

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

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

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
}

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


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 и по-прежнему видеть/отлаживать их как независимые результаты в окне Test runner:

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

надеюсь, что это помогает.


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

реализовать пользовательский MyTheoryAttribute расширение FactAttribute вместе с MyTheoryDiscoverer реализации IXunitTestCaseDiscoverer и несколько пользовательских MyTestCases расширения TestMethodTestCase и реализации IXunitTestCase по своему вкусу. Пользовательские тестовые наборы должны распознаваться MyTheoryDiscoverer и использоваться для инкапсуляции перечисленные тестовые случаи теории в форме, видимой для xUnit framework, даже если переданные значения не сериализуются изначально 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


на данный момент 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#