Podczas pisania testów (czy to jednostkowych, czy integracyjnych, czy e2e) prawie zawsze musimy stworzyć obiekt z danymi. Czasem jest to obiekt wejściowy, czasem wyjściowy. Z reguły nie interesuje nas większość pól danego obiektu, a jedynie kilka konkretnych. Mimo to musimy uzupełnić wszystkie pola, aby aplikacja zachowywała się poprawnie.
Tu z pomocą może nam przyjść biblioteka AutoFixture.
AutoFixture
Jest to narzędzie, które umożliwia automatyczne uzupełnianie właściwości obiektu losowymi wartościami.
Załóżmy, że mamy dwie klasy:
public class SampleClass
{
public int IntValue { get; set; }
public string StringValue { get; set; }
public OtherClass OtherClass { get; set; }
}
public class OtherClass
{
public decimal DecimalValue { get; set; }
public DateTime DateTimeValue { get; set; }
}
AutoFixture potrafi wygenerować taki obiekt, który przykładowo wygląda tak:
Kod który wygenerował nam ten obiekt wygląda następująco:
var fixture = new Fixture();
var sampleClass = fixture.Create<SampleClass>();
Najpierw musimy utworzyć obiekt klasy Fixture (można to zrobić globalnie). Następnie na tym obiekcie wywołujemy metodę generyczną Create, przekazując jej typ obiektu, który chcemy utworzyć.
Jak widać na załączonych obrazkach – AutoFixture wypełnił losowymi wartościami również obiekty wewnątrz naszego obiektu.
Po ponownym uruchomieniu testów wszystkie wartości będą inne, ponieważ są one generowane losowo.
Dodatkowe funkcje
Without
Może się zdarzyć sytuacja, gdy nie chcemy, aby AutoFixture uzupełnił jakąś wartość. Wtedy należy użyć metody Without:
var fixture = new Fixture();
var sampleClass = fixture
.Build<SampleClass>()
.Without(w => w.StringValue)
.Create();
Tym razem jednak przed użyciem metody Create, użyliśmy metody Build. Metodę Build używamy, gdy chcemy wprowadzić jakieś modyfikacje w obiekcie przed użyciem metody Create.
Wygenerowany obiekt wygląda następująco:
Właściwość StringValue nie została uzupełniona.
With
Czasami chcemy, aby dla jakiejś właściwości zamiast wartości losowej pojawiła się wartość konkretna. Tutaj świetnie sprawdzi się metoda With:
var fixture = new Fixture();
var sampleClass = fixture
.Build<SampleClass>()
.With(w => w.StringValue, "correct value")
.Create();
Tym razem zamiast losowej wartości dla właściwości StringValue otrzymamy napis „correct value”:
Napis konkretnej długości
Mamy kilka możliwości określenia długości napisu. Pierwszą z nich jest po prostu użycie metody Substring:
var correctString = fixture.Create<string>().Substring(0, 10);
W naszej klasie SampleClass możemy to wykorzystać na dwa sposoby. Pierwszy sposób, to wykorzystanie metody With:
var fixture = new Fixture();
var sampleClass = fixture
.Build<SampleClass>()
.With(w => w.StringValue, fixture.Create<string>().Substring(0, 10))
.Create();
Drugi to użycie metody Without:
var fixture = new Fixture();
var sampleClass = fixture
.Build<SampleClass>()
.Without(w => w.StringValue)
.Create();
sampleClass.StringValue = fixture.Create<string>().Substring(0, 10);
Możemy też zmodyfikować naszą klasę i wykorzystać atrybut StringLength:
public class SampleClass
{
public int IntValue { get; set; }
[StringLength(10)]
public string StringValue { get; set; }
public OtherClass OtherClass { get; set; }
}
A następnie po prostu użyć metody Create:
var fixture = new Fixture();
var sampleClass = fixture.Create<SampleClass>();
Każdy ze sposobów da nam finalnie obiekt klasy SampleClass z uzupełnioną właściwością StringValue, która będzie miała losowy napis o długości 10 znaków:
Jeśli byśmy chcieli, aby wszystkie nasze napisy miały określoną długość, to możemy wykorzystać Customizations z obiektu fixture:
fixture.Customizations.Add(
new StringGenerator(() => Guid.NewGuid().ToString().Substring(0, 10))
);
Dodanie takiej modyfikacji spowoduje, że każdy wygenerowany napis będzie miał długość 10.
Niestety ta metoda ma pewien minus. Domyślny algorytm (StringGenerator) na początku wygenerowanego napisu dodaje nazwę właściwości. Zatem finalnie nasz wygenerowany obiekt będzie wyglądał następująco:
Jednakże ten sposób działa poprawnie, gdy generujemy sam napis. Możemy więc zawsze zrobić coś takiego:
var fixture = new Fixture();
fixture.Customizations.Add(
new StringGenerator(() => Guid.NewGuid().ToString().Substring(0, 10))
);
var sampleClass = fixture
.Build<SampleClass>()
.With(w => w.StringValue, fixture.Create<string>())
.Create();
Teraz ponownie udało nam się otrzymać to co chcieliśmy:
Oczywiście istnieje możliwość napisania własnego modyfikatora (w tym celu należy stworzyć klasę implementującą interfejs ISpecimenBuilder), który będzie działał dokładnie tak jak chcemy.
Data z zakresu
Dodatkowy modyfikator świetnie się sprawdzi, gdy chcemy, aby wszystkie wygenerowane daty były z konkretnego przedziału.
Załóżmy, że mamy następującą klasę:
public class ClassWithDates
{
public DateTime DateTime1 { get; set; }
public DateTime DateTime2 { get; set; }
public DateTime DateTime3 { get; set; }
}
Gdy stworzymy tę klasę z wykorzystaniem metody Create:
var fixture = new Fixture();
var classWithDates = fixture.Create<ClassWithDates>();
To otrzymamy następujący obiekt:
Jeśli jednak dodamy modyfikator RandomDateTimeSequenceGenerator:
var fixture = new Fixture();
fixture.Customizations.Add(
new RandomDateTimeSequenceGenerator(
new DateTime(2019, 12, 1), new DateTime(2019, 12, 31)
)
);
var classWithDates = fixture.Create<ClassWithDates>();
To nasz obiekt będzie wyglądał następująco:
Jak widać tym razem również wszystkie daty są losowe, jednakże są one dodatkowo z konkretnego przedziału.
Podsumowanie
Oczywiście, to nie wszystkie funkcje jakie udostępnia biblioteka AutoFixture. Jest ich dużo, dużo więcej. Przedstawiłem tylko przykłady, które najczęściej przydają mi się podczas pisania testów. Zachęcam do poznania bliżej tej biblioteki, ponieważ według mnie zdecydowanie ułatwia pracę z testami.
Pingback: dotnetomaniak.pl
Możliwość komentowania została wyłączona.