TDD z wzorcem repozytorium

10

W moim nowym projekcie postanowiłem spróbować z TDD. I na samym początku napotkałem problem. Pierwszą rzeczą, którą chcę zrobić w mojej aplikacji, jest umożliwienie odczytu danych ze źródła danych. W tym celu chcę użyć wzorca repozytorium. I teraz:

  • Jeśli testy mają na celu rzeczywistą implementację interfejsu repozytorium, będę testował klasę, która ma dostęp do bazy danych i wiem, że powinienem tego unikać.
  • Jeśli testy dotyczą nierealnej implementacji wzorca repozytorium, będę dobrze testować ... po prostu kpić. W tych testach jednostkowych nie będzie żadnego kodu produkcyjnego przetestowanego.

Myślę o tym od dwóch dni i wciąż nie mogę znaleźć żadnego rozsądnego rozwiązania. Co powinienem zrobić?

Thaven
źródło

Odpowiedzi:

11

Repozytorium tłumaczy z Twojej domeny na środowisko DAL, takie jak NHibernate lub Doctrine, lub klasy wykonujące SQL. Oznacza to, że twoje repozytorium będzie wywoływać metody na tym frameworku w celu wykonania swoich obowiązków: twoje repozytorium konstruuje zapytania potrzebne do pobrania danych. Jeśli nie używasz frameworka ORM (mam nadzieję, że tak ...), repozytorium byłoby miejscem, w którym budowane są surowe instrukcje SQL.

Najbardziej podstawową z tych metod jest save: w większości przypadków po prostu przekaże obiekt z repozytorium do jednostki pracy (lub sesji).

public void Save(Car car)
{
    session.Save(car);
}

Ale spójrzmy na inny przykład, na przykład pobranie samochodu po jego identyfikatorze. Może to wyglądać

public function GetCarWithId(String id)
{
    return Session.QueryOver<Car>()
                    .Where(x => x.Id == id)
                    .SingleOrDefault();
}

Wciąż nie jest to zbyt skomplikowane, ale możesz sobie wyobrazić w wielu warunkach (zdobądź wszystkie samochody wyprodukowane po 2010 roku dla wszystkich marek w grupie „Volkswagen”), to staje się trudne. Więc w prawdziwym stylu TDD musisz to przetestować. Można to zrobić na kilka sposobów.

Opcja 1: wyśmiewać wywołania wykonane w ramach ORM

Jasne, możesz kpić z obiektu sesji i po prostu stwierdzić, że wykonano właściwe wywołania. Chociaż testuje repozytorium, to naprawdę nie jest test- napędzany bo jesteś po prostu testuje że repozytorium wewnętrznie wygląda tak, jak chcesz go. Test zasadniczo mówi „kod powinien wyglądać tak”. Mimo to jest to prawidłowe podejście, ale wydaje się, że ten rodzaj testu ma bardzo małą wartość.

Opcja 2: (Re) zbuduj bazę danych z testów

Niektóre frameworki DAL umożliwiają budowanie kompletnej struktury bazy danych na podstawie plików mapowania tworzonych w celu mapowania domeny na tabele. W przypadku tych frameworków sposobem testowania repozytoriów jest często tworzenie bazy danych z bazą danych w pamięci w pierwszym kroku testu i dodawanie obiektów za pomocą struktury DAL do bazy danych w pamięci. Następnie możesz użyć repozytorium w bazie danych w pamięci, aby sprawdzić, czy metody działają. Testy te są wolniejsze, ale bardzo ważne i przeprowadzają testy. Wymaga to pewnej współpracy ze strony środowiska DAL.

Opcja 3: Test na rzeczywistej bazie danych

Innym podejściem jest testowanie rzeczywistej bazy danych i izolowanie najbardziej nieprzystosowanych. Możesz to zrobić na kilka sposobów: otoczyć testy transakcją, wyczyścić ręcznie (nie byłoby to tak trudne do utrzymania), całkowicie odbudować bazę danych po każdym kroku ... W zależności od budowanej aplikacji może to lub może nie być wykonalnym. W moich aplikacjach mogę całkowicie zbudować lokalną bazę danych programowania z kontroli źródła, a moje unittests na repozytoriach używają transakcji, aby w pełni odizolować testy od siebie (otwarta transakcja, wstawianie danych, repozytorium testów, transakcja wycofywania). Każda kompilacja najpierw konfiguruje lokalną bazę danych programowania, a następnie wykonuje transakcje izolowane od transakcji dla repozytoriów w tej lokalnej bazie danych programowania. To'

Nie testuj DAL

Jeśli używasz frameworka DAL, takiego jak NHibernate, unikaj konieczności testowania tego frameworka. Możesz przetestować pliki mapowania, zapisując, pobierając, a następnie porównując obiekt domeny, aby upewnić się, że wszystko jest w porządku (pamiętaj o wyłączeniu buforowania), ale nie jest tak wymagane, jak wiele innych testów, które powinieneś pisać. Zazwyczaj robię to głównie w przypadku zbiorów rodziców z warunkami dotyczącymi dzieci.

Podczas testowania zwrotu repozytoriów wystarczy sprawdzić, czy jakaś właściwość identyfikująca obiekt w domenie jest zgodna. Może to być identyfikator, ale w testach często bardziej korzystne jest sprawdzenie właściwości czytelnej dla człowieka. W części „zdobądź wszystkie samochody wyprodukowane po 2010 roku…” można po prostu sprawdzić, czy pięć samochodów zostało zwróconych, a tablice rejestracyjne to „wstaw listę tutaj”. Dodatkową korzyścią jest to, że zmusza cię do myślenia o sortowaniu ORAZ twój test automatycznie wymusza sortowanie. Byłbyś zaskoczony, jak wiele aplikacji albo sortuje wiele razy (wróć sortowane z bazy danych, sortuj przed utworzeniem obiektu widoku, a następnie sortuj obiekt widoku, wszystkie na tę samą właściwość na wszelki wypadek ) lub domyślnie zakładaj sortowanie repozytorium i przypadkowo usuń które były po drodze, łamiąc interfejs użytkownika.

„Test jednostkowy” to tylko nazwa

Moim zdaniem testy jednostkowe przeważnie nie powinny trafić do bazy danych. Budujesz aplikację tak, aby każdy fragment kodu, który potrzebuje danych ze źródła, robił to z repozytorium, a repozytorium jest wstrzykiwane jako zależność. Pozwala to na łatwe kpiny i wszystkie dobrodziejstwa TDD, jakie chcesz. Ale w końcu chcesz się upewnić, że twoje repozytoria wykonują swoje obowiązki, a jeśli najłatwiejszym sposobem jest trafienie do bazy danych, to niech tak będzie. Długo porzuciłem pogląd, że „testy jednostkowe nie powinny dotykać bazy danych” i dowiedziałem się, że istnieją bardzo realne powody, aby to zrobić. Ale tylko jeśli możesz to zrobić automatycznie i wielokrotnie. A pogoda nazywamy takim testem „testem jednostkowym” lub „testem integracyjnym” jest dyskusyjna.

JDT
źródło
3
Testy jednostkowe i testy integracyjne mają różne cele. Nazwy tych testów są nie tylko dekoracyjne; są również opisowe.
Robert Harvey
9
  1. Nie testuj trywialnych lub oczywistych metod repozytorium.

    Jeśli metody są trywialnymi operacjami CRUD, wszystko, co naprawdę testujesz, to to, czy parametry są poprawnie odwzorowane. Jeśli masz testy integracyjne, takie błędy i tak staną się natychmiast widoczne.

    Ta sama zasada dotyczy trywialnych właściwości, jak ta:

    public property SomeProperty
    {
        get { return _someProperty; }
        set { _someProperty = value; }
    }
    

    Nie testujesz tego, ponieważ nie ma nic do przetestowania. We właściwości nie ma sprawdzania poprawności ani innej logiki, która wymaga weryfikacji.

  2. Jeśli nadal chcesz przetestować te metody ...

    Drogi to sposób. Pamiętaj, że są to testy jednostkowe. Nie testujesz bazy danych za pomocą testów jednostkowych; po to są testy integracyjne.

Więcej informacji
Pełny stos, część 3: Budowanie repozytorium przy użyciu TDD (zacznij oglądać za około 16 minut).

Robert Harvey
źródło
3
Jasne, rozumiem to. Mimo to, jeśli jest to podejście TDD, nie powinienem pisać żadnego kodu, jeśli najpierw nie mam testów dla tego kodu, prawda?
Thaven
1
@Thaven - na YouTube jest seria filmów zatytułowanych „Czy TDD nie żyje?”. Oglądaj ich. Odnoszą się one do wielu interesujących punktów, z których jednym jest pogląd, że stosowanie TDD na każdym poziomie aplikacji niekoniecznie jest najlepszym pomysłem. „Brak kodu bez nieudanego testu” jest zbyt ekstremalną pozycją, jest jednym z wniosków.
Jules
2
@Jules: Co to jest tl; dw?
Robert Harvey
1
@RobertHarvey Trudno podsumować, ale najważniejsze było to, że traktowanie TDD jako religii, którą należy zawsze przestrzegać, jest błędem. Wybór użycia jest częścią kompromisu i musisz wziąć pod uwagę, że (1) możesz być w stanie pracować szybciej bez niego w przypadku niektórych problemów i (2) może popchnąć cię w kierunku bardziej złożonego rozwiązania niż potrzebujesz, szczególnie jeśli używasz wielu kpiny.
Jules
1
+1 za punkt # 1. Testy mogą być niepoprawne, po prostu są one zwykle trywialne. Nie ma sensu testować funkcji, których poprawność jest bardziej oczywista niż test. To nie jest tak, że uzyskanie 100% pokrycia kodu zbliża cię do testowania każdego możliwego wykonania programu, więc równie dobrze możesz być mądry, jeśli chodzi o miejsce, w którym spędzasz wysiłki na testowaniu.
Doval