Xunit ma fajną funkcję : możesz utworzyć jeden test z Theory
atrybutem i umieścić dane w InlineData
atrybutach, a xUnit wygeneruje wiele testów i przetestuje je wszystkie.
Chcę mieć coś takiego, ale parametry do mojej metody nie są „proste” (dane jak string
, int
, double
), ale lista mojej klasy:
public static void WriteReportsToMemoryStream(
IEnumerable<MyCustomClass> listReport,
MemoryStream ms,
StreamWriter writer) { ... }
c#
unit-testing
xunit
xunit.net
zchpit
źródło
źródło
Odpowiedzi:
W
xxxxData
XUnit jest wiele atrybutów. Sprawdź na przykładPropertyData
atrybut.Możesz zaimplementować właściwość, która zwraca
IEnumerable<object[]>
. Każdaobject[]
wygenerowana przez tę metodę zostanie następnie „rozpakowana” jako parametry dla pojedynczego wywołania[Theory]
metody.Inną opcją jest
ClassData
, która działa tak samo, ale umożliwia łatwe współdzielenie „generatorów” między testami w różnych klasach / przestrzeniach nazw, a także oddziela „generatory danych” od rzeczywistych metod testowych.Zobacz np. Te przykłady stąd :
Przykład 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 } }; } } }
Przykład 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(); } }
źródło
static
. Właśnie dlatego nie zrobiłbym tego. ClassData jest wtedy, gdy chcesz uciec od statyki. W ten sposób można łatwiej ponownie wykorzystać (tj. Zagnieżdżać) generatory.[MemberData("{static member}", MemberType = typeof(MyClass))]
aby zamienićClassData
atrybut.nameof
słowa kluczowego zamiast sztywnego kodowania nazwy właściwości (łatwo się psuje, ale cicho).Aby zaktualizować odpowiedź @ Quetzalcoatl: Atrybut
[PropertyData]
został zastąpiony przez,[MemberData]
który przyjmuje jako argument nazwę ciągu dowolnej metody statycznej, pola lub właściwości, która zwracaIEnumerable<object[]>
. (Uważam, że szczególnie fajnie jest mieć metodę iteratora, która może faktycznie obliczać przypadki testowe pojedynczo, uzyskując je w miarę ich obliczania).Każdy element w sekwencji zwracany przez moduł wyliczający jest
object[]
i każda tablica musi mieć taką samą długość i ta długość musi być liczbą argumentów dla twojego przypadku testowego (z adnotacją z atrybutem[MemberData]
i każdy element musi mieć ten sam typ co odpowiadający mu parametr metody (A może mogą to być typy zamienne, nie wiem).(Zobacz informacje o wydaniu xUnit.net z marca 2014 r. I aktualną poprawkę z przykładowym kodem ).
źródło
Załóżmy, że mamy złożoną klasę samochodu, która ma klasę producenta:
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; } }
Zamierzamy wypełnić i zdać klasę samochodów do testu teorii.
Stwórz więc klasę „CarClassData”, która zwraca instancję klasy Car, jak poniżej:
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(); }
Pora na stworzenie metody testowej (CarTest) i zdefiniowanie samochodu jako parametru:
[Theory] [ClassData(typeof(CarClassData))] public void CarTest(Car car) { var output = car; var result = _myRepository.BuyCar(car); }
Powodzenia
źródło
Tworzenie anonimowych tablic obiektów nie jest najłatwiejszym sposobem konstruowania danych, więc użyłem tego wzorca w moim projekcie
Najpierw zdefiniuj kilka wspólnych klas wielokrotnego użytku
//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; } }
Teraz Twoje indywidualne dane testowe i składowe są łatwiejsze do napisania i wyczyszczenia ...
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()); } } }
Właściwość string
Description
polega na rzucaniu sobie kości, gdy jeden z wielu przypadków testowych zawiedzieźródło
Możesz spróbować w ten sposób:
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); } }
Utwórz kolejną klasę do przechowywania danych testowych:
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; } } }
źródło
Na moje potrzeby chciałem po prostu przeprowadzić serię „użytkowników testowych” przez niektóre testy - ale [ClassData] itp. Wydawało mi się przesadą w stosunku do tego, czego potrzebowałem (ponieważ lista elementów była zlokalizowana dla każdego testu).
Zrobiłem więc co następuje, z tablicą wewnątrz testu - indeksowaną z zewnątrz:
[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); }
To osiągnęło mój cel, zachowując przy tym jasny cel testu. Musisz tylko zsynchronizować indeksy, ale to wszystko.
Wygląda ładnie w wynikach, można ją zwijać i możesz ponownie uruchomić konkretną instancję, jeśli pojawi się błąd:
źródło
MemberData
wydaje się być to, że nie można wyświetlić ani uruchomić testu z określonym wejściem testowym. To jest do bani.MemberData
jeśli używaszTheoryData
i opcjonalnieIXunitSerializable
. Więcej informacji i przykładów tutaj ... github.com/xunit/xunit/issues/429#issuecomment-108187109Tak rozwiązałem twój problem, miałem ten sam scenariusz. Tak więc w linii z obiektami niestandardowymi i inną liczbą obiektów w każdym przebiegu.
[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); }
Więc to jest mój test jednostkowy, zwróć uwagę na parametr params . Pozwala to na przesłanie innej ilości obiektów. A teraz moja klasa 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(); }
Mam nadzieję, że to pomoże !
źródło
Myślę, że tu się mylisz. Co
Theory
właściwie oznacza atrybut xUnit : Chcesz przetestować tę funkcję, wysyłając specjalne / losowe wartości jako parametry, które otrzymuje ta funkcja poddawana testowi. Oznacza to, że co można zdefiniować jako kolejnego atrybutu, takich jak:InlineData
,PropertyData
,ClassData
, etc .. będzie źródłem dla tych parametrów. Oznacza to, że należy skonstruować obiekt źródłowy, aby zapewnić te parametry. W twoim przypadku myślę, że powinieneś użyćClassData
obiektu jako źródła. Ponadto - należy pamiętać, żeClassData
dziedziczy z:IEnumerable<>
- oznacza to, że za każdym razem inny zestaw wygenerowanych parametrów będzie używany jako parametry przychodzące do testowanej funkcji, aż doIEnumerable<>
uzyskania wartości.Przykład tutaj: Tom DuPont .NET
Przykład może być nieprawidłowy - nie korzystałem z xUnit przez długi czas
źródło