У Xunit есть хорошая функция : вы можете создать один тест с Theory
атрибутом и поместить данные в InlineData
атрибуты, а xUnit сгенерирует множество тестов и протестирует их все.
Я хочу иметь что - то вроде этого, но параметры в мой метод не «простые данные» (как string
, int
, double
), но список моего класса:
public static void WriteReportsToMemoryStream(
IEnumerable<MyCustomClass> listReport,
MemoryStream ms,
StreamWriter writer) { ... }
c#
unit-testing
xunit
xunit.net
зчпить
источник
источник
Ответы:
В
xxxxData
XUnit есть много атрибутов. Посмотрите, например,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(); } }
источник
static
. Именно поэтому я бы не стал. ClassData - это когда вы хотите уйти от статики. Поступая таким образом, вы можете легче повторно использовать (то есть вкладывать) генераторы.[MemberData("{static member}", MemberType = typeof(MyClass))]
для заменыClassData
атрибута.nameof
ключевое слово вместо жесткого кодирования имени свойства (ломается легко, но незаметно).Чтобы обновить ответ @ Quetzalcoatl: атрибут
[PropertyData]
был заменен,[MemberData]
который принимает в качестве аргумента строковое имя любого статического метода, поля или свойства, возвращающегоIEnumerable<object[]>
. (Мне особенно приятно иметь метод итератора, который может фактически вычислять тестовые примеры по одному, выдавая их по мере их вычисления.)Каждый элемент в последовательности, возвращаемой перечислителем, является,
object[]
и каждый массив должен иметь одинаковую длину, и эта длина должна быть количеством аргументов вашего тестового примера (с пометкой атрибута[MemberData]
и каждый элемент должен иметь тот же тип, что и соответствующий параметр метода (Или, может быть, они могут быть конвертируемыми, я не знаю.)(См. Примечания к выпуску xUnit.net за март 2014 г. и фактический патч с примером кода .)
источник
Предположим, у нас есть сложный класс Car с классом Manufacturer:
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); }
Удачи
источник
Создание массивов анонимных объектов - не самый простой способ построения данных, поэтому я использовал этот шаблон в своем проекте.
Сначала определите несколько повторно используемых общих классов
//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, bool expectedResult, string testDescription) { 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()); } } }
Свойство строки
Description
состоит в том, чтобы бросить себе кость, когда один из ваших многочисленных тестовых примеров не удался.источник
Вы можете попробовать такой способ:
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; } } }
источник
Для моих нужд я просто хотел запустить серию «тестовых пользователей» через несколько тестов - но [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); }
Моя цель достигнута, но цель теста остается ясной. Вам просто нужно синхронизировать индексы, и все.
В результатах выглядит красиво, он складывается, и вы можете повторно запустить конкретный экземпляр, если получите ошибку:
источник
MemberData
видимому, заключается в том, что вы не можете видеть или запускать тест с определенным тестовым входом. Это отстой.MemberData
если вы используетеTheoryData
и по желаниюIXunitSerializable
. Дополнительная информация и примеры здесь ... github.com/xunit/xunit/issues/429#issuecomment-108187109Вот как я решил вашу проблему, у меня был такой же сценарий. Таким образом, встроены настраиваемые объекты и разное количество объектов при каждом запуске.
[Theory] [ClassData(typeof(DeviceTelemetryTestData))] public async Task ProcessDeviceTelemetries_TypicalDeserialization_NoErrorAsync(params DeviceTelemetry[] expected) { // Arrange var timeStamp = DateTimeOffset.UtcNow; mockInflux.Setup(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>())).ReturnsAsync("Success"); // Act var actual = await MessageProcessingTelemetry.ProcessTelemetry(JsonConvert.SerializeObject(expected), mockInflux.Object); // Assert mockInflux.Verify(x => x.ExportTelemetryToDb(It.IsAny<List<DeviceTelemetry>>()), Times.Once); Assert.Equal("Success", actual); }
Это мой модульный тест, обратите внимание на параметр params . Это позволяет отправить другое количество объектов. А теперь мой класс DeviceTelemetryTestData :
public class DeviceTelemetryTestData : IEnumerable<object[]> { public IEnumerator<object[]> GetEnumerator() { yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } }; yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } }; yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } }; yield return new object[] { new DeviceTelemetry { DeviceId = "asd" }, new DeviceTelemetry { DeviceId = "qwe" } }; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
Надеюсь, это поможет !
источник
Полагаю, вы ошиблись здесь. Что на
Theory
самом деле означает атрибут xUnit : вы хотите протестировать эту функцию, отправив специальные / случайные значения в качестве параметров, которые получает эта тестируемая функция. Это означает, что вы определяете в качестве следующего атрибута, например:InlineData
,PropertyData
,ClassData
и т.д .. будет источником для этих параметров. Это означает, что вы должны создать исходный объект для предоставления этих параметров. В вашем случае, я думаю, вы должны использоватьClassData
объект в качестве источника. Также - обратите внимание, чтоClassData
наследуется от:IEnumerable<>
- это означает, что каждый раз другой набор сгенерированных параметров будет использоваться в качестве входящих параметров для тестируемой функции, пока не будут полученыIEnumerable<>
значения.Пример здесь: Tom DuPont .NET
Пример может быть неверным - долго не пользовался xUnit
источник