Przekazać złożone parametry do [teorii]
Xunit ma fajną funkcję : możesz utworzyć jeden test z atrybutem Theory
i umieścić dane w atrybutach InlineData
, a xUnit wygeneruje wiele testów i przetestuje je wszystkie.
Chcę mieć coś takiego, ale parametry mojej metody nie są " prostymi danymi "(jak string
, int
, double
), ale lista mojej klasy:
public static void WriteReportsToMemoryStream(
IEnumerable<MyCustomClass> listReport,
MemoryStream ms,
StreamWriter writer) { ... }
8 answers
Istnieje wiele xxxxData
atrybutów w XUnit. Sprawdź na przykład atrybut PropertyData
.
Możesz zaimplementować właściwość, która zwraca IEnumerable<object[]>
. Każda object[]
wygenerowana przez tę metodę zostanie następnie "rozpakowana" jako parametry dla pojedynczego wywołania metody [Theory]
.
Inną opcją jest ClassData
, która działa tak samo, ale pozwala łatwo współdzielić "Generatory" między testami w różnych klasach/przestrzeniach nazw, a także oddziela "Generatory danych" od rzeczywistego testu metody.
Patrz np. te przykłady stąd :
PropertyData Przykład
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(); }
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-22 21:24:10
Aby zaktualizować odpowiedź @Quetzalcoatl: atrybut [PropertyData]
został zastąpiony przez [MemberData]
, który przyjmuje jako argument nazwę łańcuchową dowolnej statycznej metody, pola lub właściwości, która zwraca IEnumerable<object[]>
. (Uważam, że szczególnie miło jest mieć metodę iteracyjną, która może faktycznie obliczać przypadki testowe pojedynczo, dając je w miarę ich obliczania.)
Każdy element w sekwencji zwracany przez enumerator jest {[3] } i każda tablica musi być tej samej długości i ta długość musi być liczba argumentów do twojego przypadku testowego (przypisana atrybutem [MemberData]
i każdy element musi mieć ten sam typ co odpowiedni parametr metody. (A może mogą to być kabriolety, Nie wiem.)
(Zobacz uwagi do wydania xUnit.net Marzec 2014 i aktualna łatka z przykładowym kodem .)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-08 13:33:35
Załóżmy, że mamy klasę samochodów złożonych, 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; }
}
Wypełnimy i zdamy klasę samochodu na test teoretyczny.
Więc utwórz klasę 'CarClassData', która zwróci 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();
}
Nadszedł czas 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55
Tworzenie anonimowych tablic obiektowych nie jest najłatwiejszym sposobem konstruowania danych, więc użyłem tego wzorca w moim projekcie.
Najpierw zdefiniuj niektóre klasy współdzielone 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, TExpectedOutput> : 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 członkowskie są łatwiejsze do napisania i czystsze...
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 rzuceniu sobie Kości, gdy jeden z Twoich wielu przypadków testowych nie powiedzie się.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-12-22 21:11:05
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 inną 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;
}
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-27 19:57:42
Dla moich potrzeb chciałem po prostu uruchomić serię 'test userów' przez kilka testów-ale [ClassData] itp. wydawało mi się, że to przesada (bo lista elementów była zlokalizowana przy każdym teście).
Więc zrobiłem 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, utrzymując intencje testu jasno. Musisz tylko synchronizować indeksy, ale to wszystko.
Ładnie wygląda w wynikach, jest do przewijania, a Ty można ponownie uruchomić konkretną instancję, Jeśli pojawi się błąd:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-29 20:44:48
Tak rozwiązałem twój problem, miałem ten sam scenariusz. Tak więc inline z obiektami niestandardowymi i inną liczbą obiektów na każdym uruchomieniu.
[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 wysłanie innej liczby obiektów. A teraz moja DeviceTelemetryTestData klasa:
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 !Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-03-06 14:09:47
Chyba się mylisz. Co atrybut xUnit Theory
właściwie oznacza: chcesz przetestować tę funkcję wysyłając specjalne / losowe wartości jako parametry, które otrzymuje ta funkcja-under-test. Oznacza to, że to, co definiujesz jako następny atrybut, np.: InlineData
, PropertyData
, ClassData
, itd.. będzie źródłem tych parametrów. Oznacza to, że należy skonstruować obiekt źródłowy, aby zapewnić te parametry. W Twoim przypadku powinieneś użyć ClassData
object jako źródła. Również-należy pamiętać, że ClassData
inherits from: IEnumerable<>
- oznacza to, że za każdym razem inny zestaw wygenerowanych parametrów będzie używany jako parametry przychodzące dla funkcji-under-test, dopóki IEnumerable<>
nie wygeneruje wartości.
Przykład tutaj: Tom DuPont. NET
Przykład może być niepoprawny-długo nie korzystałem z xUnit
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-19 11:31:31