Я потратил много времени, пытаясь понять это в своем проекте. Это связанное обсуждение 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