Передайте комплексные параметры [Теория]
Xunit имеет приятную функцию: вы можете создать один тест с атрибутом Theory
и поместить данные в атрибуты InlineData
, а xUnit будет генерировать множество тестов, и проверить их все.
Я хочу иметь что-то подобное, но параметры для моего метода не являются "простыми данными" (например, string
, int
, double
), но список моего класса:
public static void WriteReportsToMemoryStream(
IEnumerable<MyCustomClass> listReport,
MemoryStream ms,
StreamWriter writer) { ... }
Ответы
Ответ 1
В XUnit есть много атрибутов xxxxData
. Посмотрите, например, атрибут PropertyData
.
Вы можете реализовать свойство, которое возвращает IEnumerable<object[]>
. Каждый object[]
, который генерирует этот метод, будет затем "распакован" в качестве параметров для одного вызова вашего метода [Theory]
.
Другой вариант - ClassData
, который работает так же, но позволяет легко обмениваться "генераторами" между тестами в разных классах/пространствах имен, а также отделяет "генераторы данных" от реальных методов тестирования.
См., Например, эти примеры здесь:
Пример PropertyData
public class StringTests2
{
[Theory, PropertyData(nameof(SplitCountData))]
public void SplitCount(string input, int expectedCount)
{
var actualCount = input.Split(' ').Count();
Assert.Equal(expectedCount, actualCount);
}
public static IEnumerable<object[]> SplitCountData
{
get
{
// Or this could read from a file. :)
return new[]
{
new object[] { "xUnit", 1 },
new object[] { "is fun", 2 },
new object[] { "to test with", 3 }
};
}
}
}
Пример ClassData
public class StringTests3
{
[Theory, ClassData(typeof(IndexOfData))]
public void IndexOf(string input, char letter, int expected)
{
var actual = input.IndexOf(letter);
Assert.Equal(expected, actual);
}
}
public class IndexOfData : IEnumerable<object[]>
{
private readonly List<object[]> _data = new List<object[]>
{
new object[] { "hello world", 'w', 6 },
new object[] { "goodnight moon", 'w', -1 }
};
public IEnumerator<object[]> GetEnumerator()
{ return _data.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator()
{ return GetEnumerator(); }
}
Ответ 2
Чтобы обновить ответ @Quetzalcoatl: атрибут [PropertyData]
был заменен на [MemberData]
, который принимает в качестве аргумента имя строки любого статического метода, поля или свойства, которое возвращает IEnumerable<object[]>
. (Мне особенно приятно иметь метод итератора, который может фактически вычислять тестовые примеры по одному, вызывая их по мере их вычисления.)
Каждый элемент в последовательности, возвращаемой перечислителем, является object[]
, и каждый массив должен иметь одинаковую длину, а длина должна быть числом аргументов в вашем тестовом случае (аннотируется с атрибутом [MemberData]
, и каждый элемент должен имеют тот же тип, что и соответствующий параметр метода. (Или, может быть, они могут быть конвертируемыми типами, я не знаю.)
(см. примечания к выпуску для xUnit.net March 2014 и фактический патч с примером кода).
Ответ 3
Создание массивов анонимных объектов - не самый простой способ построения данных, поэтому я использовал этот шаблон в своем проекте
Сначала определите некоторые повторно используемые общие классы
//http://stackoverflow.com/questions/22093843
public interface ITheoryDatum
{
object[] ToParameterArray();
}
public abstract class TheoryDatum : ITheoryDatum
{
public abstract object[] ToParameterArray();
public static ITheoryDatum Factory<TSystemUnderTest, TExpectedOutput>(TSystemUnderTest sut, TExpectedOutput expectedOutput, string description)
{
var datum= new TheoryDatum<TSystemUnderTest, TExpectedOutput>();
datum.SystemUnderTest = sut;
datum.Description = description;
datum.ExpectedOutput = expectedOutput;
return datum;
}
}
public class TheoryDatum<TSystemUnderTest, TExecptedOutput> : TheoryDatum
{
public TSystemUnderTest SystemUnderTest { get; set; }
public string Description { get; set; }
public TExpectedOutput ExpectedOutput { get; set; }
public override object[] ToParameterArray()
{
var output = new object[3];
output[0] = SystemUnderTest;
output[1] = ExpectedOutput;
output[2] = Description;
return output;
}
}
Теперь ваши индивидуальные тестовые данные и данные об участниках стали проще и понятнее...
public class IngredientTests : TestBase
{
[Theory]
[MemberData(nameof(IsValidData))]
public void IsValid(Ingredient ingredient, string testDescription, bool expectedResult)
{
Assert.True(ingredient.IsValid == expectedResult, testDescription);
}
public static IEnumerable<object[]> IsValidData
{
get
{
var food = new Food();
var quantity = new Quantity();
var data= new List<ITheoryDatum>();
data.Add(TheoryDatum.Factory(new Ingredient { Food = food } , false, "Quantity missing"));
data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity } , false, "Food missing"));
data.Add(TheoryDatum.Factory(new Ingredient { Quantity = quantity, Food = food } , true, "Valid"));
return data.ConvertAll(d => d.ToParameterArray());
}
}
}
Свойство string Description
состоит в том, чтобы бросить себе кость, когда один из ваших многочисленных тестов не пройден
Ответ 4
Вы можете попробовать следующим образом:
public class TestClass {
bool isSaturday(DateTime dt)
{
string day = dt.DayOfWeek.ToString();
return (day == "Saturday");
}
[Theory]
[MemberData("IsSaturdayIndex", MemberType = typeof(TestCase))]
public void test(int i)
{
// parse test case
var input = TestCase.IsSaturdayTestCase[i];
DateTime dt = (DateTime)input[0];
bool expected = (bool)input[1];
// test
bool result = isSaturday(dt);
result.Should().Be(expected);
}
}
Создайте еще один класс для хранения тестовых данных:
public class TestCase
{
public static readonly List<object[]> IsSaturdayTestCase = new List<object[]>
{
new object[]{new DateTime(2016,1,23),true},
new object[]{new DateTime(2016,1,24),false}
};
public static IEnumerable<object[]> IsSaturdayIndex
{
get
{
List<object[]> tmp = new List<object[]>();
for (int i = 0; i < IsSaturdayTestCase.Count; i++)
tmp.Add(new object[] { i });
return tmp;
}
}
}
Ответ 5
Предположим, что у нас есть сложный класс Car с классом производителя:
public class Car
{
public int Id { get; set; }
public long Price { get; set; }
public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
public string Name { get; set; }
public string Country { get; set; }
}
Мы собираемся заполнить и сдать класс Car для теста по теории.
Поэтому создайте класс 'CarClassData', который возвращает экземпляр класса Car, как показано ниже:
public class CarClassData : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
yield return new object[] {
new Car
{
Id=1,
Price=36000000,
Manufacturer = new Manufacturer
{
Country="country",
Name="name"
}
}
};
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Это время для создания тестового метода (CarTest) и определения автомобиля в качестве параметра:
[Theory]
[ClassData(typeof(CarClassData))]
public void CarTest(Car car)
{
var output = car;
var result = _myRepository.BuyCar(car);
}
![complex type in theory]()
Удачи
Ответ 6
Для своих нужд я просто хотел провести серию "тестовых пользователей" через несколько тестов - но [ClassData] и т.д. Казались излишними для того, что мне было нужно (потому что список элементов был локализован для каждого теста).
Итак, я сделал следующее с массивом внутри теста - проиндексированным извне:
[Theory]
[InlineData(0)]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Account_ExistingUser_CorrectPassword(int userIndex)
{
// DIFFERENT INPUT DATA (static fake users on class)
var user = new[]
{
EXISTING_USER_NO_MAPPING,
EXISTING_USER_MAPPING_TO_DIFFERENT_EXISTING_USER,
EXISTING_USER_MAPPING_TO_SAME_USER,
NEW_USER
} [userIndex];
var response = await Analyze(new CreateOrLoginMsgIn
{
Username = user.Username,
Password = user.Password
});
// expected result (using ExpectedObjects)
new CreateOrLoginResult
{
AccessGrantedTo = user.Username
}.ToExpectedObject().ShouldEqual(response);
}
Это позволило мне достичь цели, сохранив при этом цель теста. Вам просто нужно синхронизировать индексы, но это все.
Результаты хорошо выглядят, они сворачиваются, и вы можете повторно запустить конкретный экземпляр, если вы получили ошибку:
![enter image description here]()
Ответ 7
Я думаю, вы ошибаетесь здесь. Какой атрибут xUnit Theory
на самом деле означает: вы хотите протестировать эту функцию, отправив специальные/случайные значения в качестве параметров, которые получает эта функция-under-test. Это означает, что то, что вы определяете как следующий атрибут, например: InlineData
, PropertyData
, ClassData
и т.д., Будет источником этих параметров. Это означает, что вы должны создать исходный объект для предоставления этих параметров. В вашем случае, я думаю, вы должны использовать объект ClassData
как источник. Кроме того - обратите внимание, что ClassData
наследует от: IEnumerable<>
- это означает, что каждый раз, когда другой набор сгенерированных параметров будет использоваться в качестве входящих параметров для проверки функции до тех пор, пока IEnumerable<>
не выдаст значения.
Пример здесь: Tom DuPont.NET
Пример может быть неправильным - я долгое время не использовал xUnit