fbpx
devstyle.pl - Blog dla każdego programisty
devstyle.pl - Blog dla każdego programisty
3 minut

IUseFixture<T>


19.12.2013

Był taki smutny czas, że struktura dziedziczenia w moich testach zdecydowanie przerastała stopniem skomplikowania dziedziczenie w testowanym kodzie. A bo jedna klasa bazowa dla testów umożliwiała na przykład kontakt z prawdziwą bazą. Inna – testy z bazą in-memory. Jeszcze inna – puszczanie requestów do systemu. I tak dalej.

Efekt był taki, że de facto wszystkie testy dziedziczyły ze wszystkiego. Fuj na maxa, z pryszczem jeszcze ohydnym.

Wszystko to uległo znaczącej poprawie, gdy nauczyłem się korzystać z interfejsu IUseFixture<T> w xunit. Celowo piszę “nauczyłem się korzystać” a nie “dowiedziałem się że istnieje”, ponieważ między tymi dwoma momentami minęło bez mała półtora roku.

Co to jest IUseFixture? Jest to prosty sposób na powiedzenie runnerowi: “testy w tej klasie potrzebują funkcjonalności oferowanej przez klasę T”.

Mógłbym dalej się “słownie” rozwodzić nad tym tematem, ale lepiej rozwieść się kodowo. Oto przykład “fixture” oferującej dostęp do nowej instancji “in memory database” z Simple.Data:

/// <summary>
/// Fixture for tests using in-memory db.
/// </summary>
public class InMemoryDbFixture : IDisposable
{
    public dynamic Db;

    public InMemoryDbFixture()
    {
        Reset();
    }

    public void Dispose()
    {
        Database.StopUsingMockAdapter();
    }

    public void Reset()
    {
        var inMemoryAdapter = new InMemoryAdapter();

        configure_schema(inMemoryAdapter);

        Database.UseMockAdapter(inMemoryAdapter);

        Db = Database.Open();
    }

    static void configure_schema(InMemoryAdapter adapter)
    {
        // keys / relations configuration
    }
}

Jak to działa? Bardzo prosto: każda moja klasa z testami sprawdzającymi poprawność wykonywanych operacji wykorzystując bazę utworzoną w pamięci przez Simple.Data (co w kolejnej wersji będzie “full-blown database” a nie tylko słownikiem na słowniku jak jest teraz) posiada taki oto kod:

private InMemoryDbFixture _db;
public void SetFixture(InMemoryDbFixture data)
{
    _db = data;
    _db.Reset();
}

A dostęp do bazy mam przez “_db.Db”. Sweet.

A dlaczego zawsze w SetFixture mam wywołanie Reset()? Bo instancja InMemoryFixture jest tworzona tylko raz dla wszystkich testów z danej klasy. W pewnych okolicznościach ma to zastosowanie, a w innych: nie. To co robię jest faktycznym zasymulowaniem klasy bazowej posiadającej w swoim konstruktorze całą logikę inicjalizacji bazy. Z tą różnicą, że teraz mogę sobie dobierać dowolne “fixtures” z których moje testy korzystają. A tych “fixtures” mam parę.

Na koniec ciekawostka: aktualnie implementacja omawianego interfejsu wymusza stworzenie metody

public void SetFixture(T data)

, która to metoda weźmie parametr “data” i przypisze go prawdopodobnie do jakiegoś pola. A jej implementacja będzie identyczna w każdej klasie, która danej zależności potrzebuje. W v2.0 xunit ma się to zmienić: zależności testów będą przekazywane w konstruktorze. How cool is that?

0 0 votes
Article Rating
5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Bart
Bart
10 years ago

Witam, dziękuję za wpis, jednak proponował bym wrzucić cały kod klasy testu. W tytule wpisu nazwa”IUseFixture”, więc szukam wykorzystania tego interfejsu w listingach i nigdzie nie ma. Dopiero zapytanie wujka googla pokazało o co tak naprawdę biega. Pisze to z punktu widzenia czytelnika, który nie korzysta na codzień z xunit.

orientman
10 years ago

Kontynuując z twittera. Pierwsza myśl po przeczytaniu twojego wpisu była taka: ojoj, znowu niepotrzebnie odkryłem amerykę i w bólach doszedłem do podobnego rozwiązania dla NUnit. A wystarczyło się rozejrzeć… Druga myśl: przecież dokładnie takie API można zaimplementować na bazie NUnit i stąd machnąłem szybko: https://gist.github.com/orient-man/8039247 (z uproszczeniem, że cykl życia fikstury to 1 test – bo tego zwykle potrzebuję, BTW nie wiem jak zrobią “W v2.0 xunit ma się to zmienić: zależności testów będą przekazywane w konstruktorze” – przecież to zmieni semantykę, gdzie zawołasz Reset? No chyba, że dla każdego testu z klasy leci nowa instancja – nie znam xunit). Napisawszy powyższe doszedłem to tego, że to w sumie nic mi nie daje ;-). Moje obecne API do fikstur wygląda +/- tak:

class MyTests: TestsWithFixtures

[SetUp]
// nadklasa posprząta w TearDown
AddFixture(new InMemoryDatabaseFixture());
// tylko przykład, aby mieć > 1 fiksturę
AddFixture(new RedirectDebugToFile(“blabla.txt”));
var account = Examples.BasicClientAccount();
….

Takie API pozwala mi ustalić kolejność fikstur np.: gdyby druga wymagała już bazy danych (stąd moje pytanie na twitterze). Ponadto jest zwięźlejsze i IMHO czytelniejsze (nie zachodzi za kurtyną żadna magia). Co prawda faza SetUp łączy faktyczny setup z fazą “given” i to mi się nie podoba tj. co ma piernik (że jakiś mechanizm persystencji musi być) do wiatraka (że klient ma konto księgowe).

W każdym razie po raz kolejny upewniłeś mnie, że musze się pobawić xunit-em. Szczególnie, że na półce czeka cegła: http://www.amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054 :)

orientman
10 years ago

Mała wtopa, książka jest o xUnit dla Javy – zupełnie innym frameworku – dzięki @gutek ;)

trackback
10 years ago

IUseFixture | Maciej Aniserowicz o programowaniu…

Dziękujemy za dodanie artykułu – Trackback z dotnetomaniak.pl…

Kurs Gita

Zaawansowany frontend

Szkolenie z Testów

Szkolenie z baz danych

Książka

Zobacz również