Jest na to wiele sposobów.
wersja Imperatyw
Oto prostsza wersja imperatyw niż dostarczony w PO:
[Fact]
public void ImperativeTest()
{
var fixture = new Fixture();
var expected = fixture.CreateMany<SyncItem>(3).OrderBy(si => si.Key);
var unorderedItems = expected.Skip(1).Concat(expected.Take(1)).ToArray();
fixture.Inject(unorderedItems);
var sut = fixture.Create<SyncItemList>();
Assert.Equal(expected, sut);
}
Podczas the default number of many items is 3, myślę, że lepiej zadzwonić go wyraźnie w tym przypadku testowego. Stosowany tutaj algorytm szyfrowania wykorzystuje fakt, że uporządkowanie sekwencji trzech (różnych) elementów, przesuwając pierwszy element do tyłu, musi skutkować nieuporządkowaną listą.
Jednak problem z tym podejściem polega na tym, że polega on na zmutowaniu modelu fixture
, więc trudno go refaktoryzować, aby uzyskać bardziej deklaratywne podejście.
dostosowaną wersję
W celu byłaby do bardziej deklaratywny wersji, można najpierw hermetyzacji algorytm kodowania w a Customization:
public class UnorderedSyncItems : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Customizations.Add(new UnorderedSyncItemsGenerator());
}
private class UnorderedSyncItemsGenerator : ISpecimenBuilder
{
public object Create(object request, ISpecimenContext context)
{
var t = request as Type;
if (t == null ||
t != typeof(SyncItem[]))
return new NoSpecimen(request);
var items = ((IEnumerable)context
.Resolve(new FiniteSequenceRequest(typeof(SyncItem), 3)))
.Cast<SyncItem>();
return items.Skip(1).Concat(items.Take(1)).ToArray();
}
}
}
rozwiązywania new FiniteSequenceRequest(typeof(SyncItem), 3))
jest po prostu słabo wpisany (nie generyczny) sposób tworzenia skończonej sekwencji instancji SyncItem
; to jest to, co robi CreateMany<SyncItem>(3)
za kulisami.
Umożliwia to byłaby test do:
[Fact]
public void ImperativeTestWithCustomization()
{
var fixture = new Fixture().Customize(new UnorderedSyncItems());
var expected = fixture.Freeze<SyncItem[]>().OrderBy(si => si.Key);
var sut = fixture.Create<SyncItemList>();
Assert.Equal(expected, sut);
}
Zauważ użycie metody Freeze. Jest to konieczne, ponieważ dostosowywanie UnorderedSyncItems
zmienia tylko sposób tworzenia instancji; nadal tworzy nową tablicę za każdym razem, gdy otrzyma taką prośbę. Freeze
zapewnia, że ta sama tablica jest ponownie używana za każdym razem - także wtedy, gdy fixture
tworzy instancję sut
.
Konwencja oparte testowanie
Powyższy test może być refactored do deklaratywnej, test konwencja opartej przez wprowadzenie atrybutu [UnorderedConventions]
:
public class UnorderedConventionsAttribute : AutoDataAttribute
{
public UnorderedConventionsAttribute()
: base(new Fixture().Customize(new UnorderedSyncItems()))
{
}
}
To jest po prostu deklaratywne klej do zastosowania dostosowania UnorderedSyncItems
. Badanie staje się teraz:
[Theory, UnorderedConventions]
public void ConventionBasedTest(
[Frozen]SyncItem[] unorderedItems,
SyncItemList sut)
{
var expected = unorderedItems.OrderBy(si => si.Key);
Assert.Equal(expected, sut);
}
Wskazówka korzystanie z [UnorderedSyncItems]
i [Frozen]
atrybutów.
Ten test jest bardzo zwięzły, ale może nie być tym, na co masz ochotę. Problem polega na tym, że zmiana zachowania jest teraz ukryta w atrybucie [UnorderedSyncItems]
, więc raczej niejawne, co się dzieje. Ja wolę używać samego dostosowania jako zestawu konwencji dla całego zestawu testów, więc nie lubię wprowadzania odmian przypadków testowych na tym poziomie. Jeśli jednak twoje konwencje stwierdzą, że instancje powinny zawsze być nieautentyczne, to zawsze należy je anulować. Ta konwencja jest dobra.
Jeśli jednak chcesz tylko użyć nieuporządkowanych tablic dla niektórych przypadków testowych, użycie atrybutu [AutoData]
nie jest najbardziej optymalnym podejściem.
deklaratywna sprawdzian
Byłoby miło, jeśli można po prostu zastosować atrybut parametr poziomu, podobnie jak atrybut [Frozen]
- być może ich łączenie, jak [Unordered][Frozen]
. Jednak to podejście nie działa.
Zawiadomienie z poprzednich przykładów, że zamówienie ma znaczenie. Przed zamrożeniem musisz zastosować UnorderedSyncItems
, ponieważ w przeciwnym razie zamrożona tablica może nie być gwarantowana.
Problem z atrybutami poziomu parametru polega na tym, że podczas kompilacji struktura .NET nie gwarantuje kolejności atrybutów, gdy biblioteka kleju AutoFixture xUnit.net odczytuje i stosuje atrybuty.
Zamiast tego można zdefiniować pojedynczy atrybut stosuje się, podobnie jak to:
public class UnorderedFrozenAttribute : CustomizeAttribute
{
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new CompositeCustomization(
new UnorderedSyncItems(),
new FreezingCustomization(parameter.ParameterType));
}
}
(FreezingCustomization
zapewnia podstawową implementację atrybutu [Frozen]
).
Pozwala to napisać ten test:
[Theory, AutoData]
public void DeclarativeTest(
[UnorderedFrozen]SyncItem[] unorderedItems,
SyncItemList sut)
{
var expected = unorderedItems.OrderBy(si => si.Key);
Assert.Equal(expected, sut);
}
Należy zauważyć, że ten test deklaratywny używa domyślnego atrybutu [AutoData]
bez żadnych dostosowań. zacji, ponieważ szyfrowanie jest teraz stosowane przez atrybut [UnorderedFrozen]
na poziomie parametru.
Umożliwi to również korzystanie z zestawu (innych) konwencji obejmujących cały pakiet testowy, zawartych w atrybucie [AutoData]
i nadal będzie używać [UnorderedFrozen]
jako mechanizmu akceptacji.
Jak wygląda "SyncItemList"? –
Wiesz, co by było naprawdę fajne, gdyby istniał atrybut i typ żądania UnOrdered, przypominający skończoną sekwencję. Potrzebujesz tylko 3 lub więcej unikatowych przedmiotów i możesz zagwarantować sekwencję poza kolejnością. – cocogorilla
Czy mogę zapytać, z której wersji AutoFixture korzystasz? [Liczby są losowe] (https://github.com/AutoFixture/AutoFixture/wiki/AutoFixture-3.0-Release-Notes#numbers-are-random) w AutoFixture 3. –