Przejdź do treści

DevStyle - Strona Główna
IUseFixture<T>
Backend

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?

Zobacz również