Тесты 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, в Test Explorer появился только один элемент вместо двух. Это потому что предоставленный объект Impl.Client
не был сериализуем ни одним из интерфейсов, поддерживаемых xUnit (подробнее об этом позже).
В моем случае я не хотел, чтобы проблемы с тестированием были включены в мой основной код. Я думал, что мог бы написать тонкий прокси вокруг моего реального класса, который обманул бы бегуна xUnit, думая, что он мог бы его сериализовать, но после борьбы с ним дольше, чем я хотел бы признать, я понял, что часть, которую я не понимал, была:
Объекты не просто сериализуются во время обнаружения для подсчета перестановок; каждый объект также десериализуется во время выполнения теста во время запуска теста.
Таким образом, любой объект, который вы предоставляете [MemberData]
должен поддерживать полную циклическую (де) сериализацию. Сейчас это кажется очевидным для меня, но я не смог найти никакой документации, пока пытался это выяснить.
Решение
Убедитесь, что каждый объект (и любой не примитив, который он может содержать) может быть полностью сериализован и десериализован. Реализация xUnit's
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
}
Немного раздражает, что я больше не вставляю целевой объект, но это всего лишь одна дополнительная строка кода для вызова моего компоновщика. И мои тесты проходят (и появляются дважды!), Поэтому я не жалуюсь.
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; }
}
Надеюсь это поможет.
В моем недавнем проекте я столкнулся с той же самой проблемой, и после некоторых исследований я нашел следующее решение:
Реализуйте свой пользовательский MyTheoryAttribute, расширяющий FactAttribute, вместе с MyTheoryDiscoverer, реализующим IXunitTestCaseDiscoverer, и несколько пользовательских MyTestCases, расширяющих 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
Пока 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:
Простая альтернатива заключается в том, что если вы используете основной проект.net, вместо использования vstest explorer вы можете запускать тесты в командной строке с помощью "dotnet test"
Результаты следующие:
- получить общий объем тестов
- сумма, которая прошла
- сумма, которая не прошла
Для неуспешных тестов данных элементов вы получите соответствующие значения параметров для каждого из неудачных тестов данных элементов.