Testowanie DAO w JPA 2.0 za pomocą DbUnit część 1
Testy jednostkowe klas typu DAO (Data Access Object) są trudne. Wynika to z samej natury tych testów. Przypomnijmy, że testy jednostkowe powinny być:
- Małe – powinny testować jedną konkretną rzecz, jednostkę kodu. W Obiektowie jest nią metoda PUBLICZNA.
- Szybkie – testy powinny trwać krótko ponieważ są często wykonywany. Im krócej trwa tym częściej je wywołujemy.
- Kompletne – czyli powinny testować zarówno dobre jak i złe dane oraz dane brzegowe. Dodatkowo warto sprawdzać odporność na awarie np. brak zależności.
- Wzajemnie niezależne – testy jednostkowe nie powinny zależeć od siebie. Kolejność wykonania powinna być dowolna i nie wpływać na wyniki.
- Autonomiczne względem środowiska – testy nie powinny korzystać z zewnętrznych źródeł danych, baz, plików czy usług. Nie powinny też modyfikować środowiska.
Ostatnie dwa punkty tworzą wrogie środowisko dla testów DAO. Spowodowane jest to przez przynajmniej częściową konieczność sięgania do bazy danych. Dlaczego? No właśnie można użyć jakiejś biblioteki mockującej i po problemie. Tyle tylko, że jest to fajne rozwiązanie jeżeli chcemy sprawdzić zachowanie DAO w przypadku zwrócenia przez bazę jakiś danych. Względnie jeżeli chcemy sprawdzić czy DAO odwołuje się do bazy danych z jakimiś konkretnymi parametrami czy wywołuje metody w odpowiedniej kolejności. Nie sprawdzimy jednak czy dane zostały rzeczywiście zapisane/usunięte/zaktualizowane/pobrane z bazy danych. Wynika to z faktu, że zamockowany obiekt zrobi to co chcemy i jak mu powiemy „zwróć obiekt” to go zwróci bez weryfikacji czy w rzeczywistości by to nastąpiło.
Szczególnie trudne jest testowanie DAO jeżeli posługujemy się JPA. Przyjrzyjmy się takiej sytuacji. Chcemy utrwalić pewien obiekt. Mockujemy EntityManagera i konfigurujemy wywołanie metody persist. Test przejdzie, ale produkcja się wywali. Dlaczego? Ponieważ w teście powtórzyliśmy błąd z implementacji… pominęliśmy rozpoczęcie i zakończenie transakcji. Mock zrobił to co mu kazaliśmy. Rzeczywisty obiekt zrobił coś innego. Test jest o kant dupy potłuc.
Pisanie mocków jest sztuką ponieważ trzeba wiedzieć jak zachowuje się rzeczywisty obiekt. Łatwiej jest podpiąć obiekt rzeczywisty tyle tylko, że nasz test jednostkowy przestaje być jednostkowy. Po pierwsze pojawia się interakcja ze środowiskiem zewnętrznym. Po drugie testy przestają być szybkie. Po trzecie jeżeli źle je zaimplementujemy to poszczególne testy będą od siebie zależeć i tym samym całe TDD bierze w łeb.
Należy ograniczyć straty. Musimy pogodzić się z interakcją ze środowiskiem. Tego nie przeskoczymy. Niestety spadnie też szybkość testów temu można częściowo zaradzić eliminując konieczność korzystania z sieci. Bazę danych stawiamy lokalnie. W idealnym przypadku może to być HSQLDB utrzymywany w pamięci. Choć czasami jest to niewygodne rozwiązanie ponieważ nie jesteśmy wstanie podejrzeć tego co rzeczywiście jest w bazie i przeprowadzić tzw. „dupa debuggingu”.
Wyeliminować można za to zależności pomiędzy testami. Pomóc może nam tu DbUnit. Biblioteka ta umożliwia łatwe tworzenie testów w których występuje interakcja z bazą danych. Sama biblioteka nie sprawi, że nasze testy będą lepsze, bo o to musimy zadbać sami. Pomoże nam jednak w dostarczeniu testów o których można powiedzieć, że są prawie dobre.
Krótkie wprowadzenie teoretyczne
By uzyskać w miarę dobre testy, które można by nazwać jednostkowymi, o ile nie jesteśmy TDD-faszystami, muszą one spełniać kilka założeń.
1. Muszą testować jedną funkcjonalność
Jest to stosunkowo proste. Jedna klasa testowa zawiera w sobie tylko jedno dao do testowania. Nie tworzymy innych dao. Jeżeli musimy stworzyć dao od którego testowane dao jest zależne to znaczy, że nasza klasa jest do dupy. Oznacza to też, że powinniśmy tworzyć tylko minimalną wymagana bazę danych.
2. Muszą być wzajemnie niezależne
To oznacza, że zarówno dwa testy w obrębie jednej klasy są od siebie niezależne jak i to, że zestaw testów nie jest uzależniony od innego zestawu.
3. Nie mogą zmieniać stanu bazy
Oznacza to, że test pozostawia bazę w takim samym stanie jak ją zastał. Jeżeli coś dodaliśmy na początku testu w ramach konfiguracji to usuwamy to z bazy.
4. Muszą być w miarę szybkie
To jest najtrudniejsze ponieważ w dużej mierze zależy od sposobu konfiguracji i wybranej metody czyszczenia bazy. Za sukces można uznać sytuację w której odpalenie testu nie oznacza, że mamy przerwę na kawę.
Te cztery założenia powinny być bazą do tworzenia naszych testów. Pozwolą one na przygotowanie testów, które później będzie można w miarę łatwo uruchamiać i rozszerzać.
Oczywiście nie wszystko jest takie piękne. Istnieje kilka pułapek, które mogą spowodować, że nasze testy będą bezsensowne.
- Źle dobrana konfiguracja bazy może spowodować, że testy będą trafiać w próżnię, czyli zawsze będą dobre, lub nie będą się uruchamiać i nie będzie wiadomo dlaczego.
- Kiepsko przemyślane asercje, które będą dostawać zawsze prawdziwe warunki.
- Ogólny burdel w kodzie, który spowoduje, że nie będziemy rozumieli co się dzieje.
Na razie tyle. Za kilka dni jak uporam się z puszką farby „Willow Creek 4” będą przykłady.