AutoFixtureCzęść NR.7 Jeżeli kiedykolwiek pracowałeś nad dużym projektem, używając TDD, to wiesz jak bardzo ważne, jest pokrycie testów. Pisanie tych testów wymaga czasu i wysiłku. Będziesz często pracował z bardzo złożonymi instancjami klas. Ich utworzenie może zajmować 80 linijek kodu lub więcej. Zanim więc napiszesz warunek testowy, już musisz dużo napisać.
Uzupełnianie obiektów bzdurnymi danymi może zajmować więcej czasu niż MOKOWANIE metody do testowania kodu.
Zresztą patrz na ten kod, który napisałem, gdy robiłem projekt w stylu TDD.
//arrange
_buyGameModel = new BuyGameModel()
{
FirstName = "Cezary",
Email = "Walenciuk@c.com",
LastName = "Walenciuk",
Game = new GameModel()
{
Id = 99,
Name = "Mortal Kombat"
}
};
_request = new GameBuyingRequest()
{
FirstName = "Cezary",
LastName = "Walenciuk",
Email = "Walenciuk@c.com",
Date = DateTime.Now,
GameToBuy = new Game() { Id = 99, Name = "Mortal Kombat" }
};
Problem też się komplikuje, gdy musisz zmienić konstruktor tych klas i nagle musisz także zmienić kod w testach. Może Ci się tak spieszy z poprawką, że nie ma czasu uzupełnić odpowiednio testy. To się może wydarzać i wszystko popsuć.
Jak więc zaradzić temu problemowi. Przydałoby się coś, co automatycznie mogło uzupełnić obiekty bzdurnymi danymi. Do akcji wskakuje AutoFixure.
Co to jest AutoFixure?
Chciałbyś uprościć swoje testy jednostkowe. Teraz możesz automatycznie uzupełnić swoje klasy, interfejsy bzdurnymi wartościami bez pisania ich samemu. Oto prosty przykład.
[Fact]
public void CreateRandomTimeSpan()
{
Fixture fixture = new Fixture();
var atimeSpan = fixture.Create<TimeSpan>();
}
[Fact]
public void CreateRandomDateTimeOffset()
{
Fixture fixture = new Fixture();
var anDateTimeOffset = fixture.Create<DateTimeOffset>();
}
Tworzenie losowych wartości dla gotowych struktur nie jest jednak takie ekscytujące. Prawdziwa potęga polega na generowaniu automatycznego obiektu na podstawie twojej klasy.
Powiedzmy, że mam taką klasę:
public class BuyGameModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public GameModel Game { get; set; }
public DateTime Date { get; set; }
}
public class GameModel
{
public int Id { get; set; }
public string Name { get; set; }
}
Instalujesz potem paczkę NuGet:
Piszesz taki kod:
[Fact]
public void PrepoluatedData()
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var gameModelPopulated = fixture.Create<BuyGameModel>();
}
Działa to tak:
Jeżeli pisałeś mnóstwo testów jednostkowych w swoim życiu, to wiesz jak TO, może skrócić twój czas na pisanie testu. Używając jednej linki kodu z AutoFixture, możesz otrzymać pełnie uzupełniony obiekt.
Co więcej, jeśli będziesz zmieniał konstruktor bądź właściwości w swojej klasie to ten kod z AutoFixture nadal będzie dział. Nawet nie wiesz jak to, jest ważne.
Z Automatyzowane testy z AutoFixture
Cały ten AutoFixure wygenerował także masę innych genialnych pomysłów. Przykładowo dzięki niemu może zautomatyzować tworzenie testów, jeśli wiesz jak.
Jeżeli korzystasz z Dependency injection, to zapewne wiesz, że warto pisać mechanizmy sprawdzające NULLE w argumentach konstruktora, które fachowo nazywamy GuardClause.
Oto przykładowa klasa, która wstrzykuje do siebie dwie zależności. Sprawdzam w GuardClause czy te parametry są NULL, jeśli są, to wyrzucamy wyjątek.
public class Example
{
private IDependency _a, _b;
public Example(IDependency one, IDependency two)
{
_a = one ?? throw new ArgumentNullException(nameof(one));
_b = two ?? throw new ArgumentNullException(nameof(two));
}
}
public class Dependency : IDependency
{}
public interface IDependency
{}
Teraz nastaje pytanie, jak to przetestować. Jak napisać przykład testowy? Problem też polega na tym, że prawdopodobnie chciałbyś przetestować wiele klas, które mają mechanizm wstrzykiwania zależności
public class AnotherExample
{
private IDependency _a, _b;
public AnotherExample(IDependency one, IDependency two)
{
_a = one;
_b = two;
}
}
Dzięki tym testu znaleźć klasę, która jest źle napisana i nie ma GuardClause.
Mógłbyś napisać takie testy i sprawdzać różne parametry w konstruktorze, ale wydaje się to bez sensu.
[Fact]
public void TestingGuard()
{
Assert.Throws(typeof(NullReferenceException),
() => { new Example(null, null); });
}
[Fact]
public void TestingGuard2()
{
Assert.Throws(typeof(NullReferenceException),
() => { new Example(new Dependency(), null); });
}
[Fact]
public void TestingGuard3()
{
Assert.Throws(typeof(NullReferenceException),
() => { new Example(null, new Dependency()); });
}
Instalujesz paczkę AutoFixture.Idioms. Zawiera ona kolekcję mechanizmów, które mogą zautomatyzować pisanie testów.
Chcemy więc zautomatyzować test "GuardClause" dla wielu klas. Używając XUnit i Idiomu GuardClauseAssertion mogę napisać taki test następującą.
[Theory]
[InlineData(typeof(Example))]
[InlineData(typeof(AnotherExample))]
//testing as many classes as want
public void EnsureConstructorArgumentsNotNull(Type type)
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new AutoFixture.Idioms.
GuardClauseAssertion(fixture);
assertion.Verify(type.GetConstructors());
}
W przypadku nieudanego testu dostaje nawet konkretną wiadomość o błędzie:
Message:
AutoFixture.Idioms.GuardClauseException : An attempt was made to assign the value null to the parameter "one" of the method ".ctor", and no Guard Clause prevented this. Are you missing a Guard Clause?
Method Signature: Void .ctor(AutoFixtureExampleTest.IDependency, AutoFixtureExampleTest.IDependency)
Podsumowanie
AutoFixture to potężne narzędzie, które otwiera wiele możliwości. Nie ma co ukrywać, gdy piszesz testy, tworzysz ogromną ilość kodu boilerplate, który z ciągłymi zmianami w kodzie staje się coraz bardziej trudny do zarządzenia. Zmienisz konstruktor w jednym miejscu i 5 testów może przestać działać.
Używając AutoFixture, upraszczasz proces tworzenia testów i przyszłościowo lepiej będzie Ci się nimi zarządzało. Polecam