Pracuję z wieloma aplikacjami internetowymi, które są obsługiwane przez bazy danych o różnym stopniu złożoności w backend. Zazwyczaj istnieje warstwa ORM oddzielna od logiki biznesowej i prezentacji. To sprawia, że testowanie jednostkowe logiki biznesowej jest dość proste; rzeczy mogą być zaimplementowane w modułach dyskretnych, a wszelkie dane potrzebne do testu mogą zostać sfałszowane poprzez wyśmiewanie obiektów.
Jednak testowanie ORM i samej bazy danych zawsze było obarczone problemami i kompromisami.
Przez lata wypróbowałem kilka strategii, z których żadna nie była dla mnie satysfakcjonująca.
Załaduj testową bazę danych ze znanymi danymi. Przeprowadź testy w stosunku do ORM i potwierdź, że odpowiednie dane powracają. Wadą jest to, że testowa baza danych musi nadążać za wszelkimi zmianami schematu w bazie danych aplikacji i może nie być zsynchronizowana. Opiera się również na sztucznych danych i może nie ujawniać błędów, które pojawiają się z powodu głupiego wkładu użytkownika. Wreszcie, jeśli testowa baza danych jest mała, nie ujawni ona nieefektywności takich jak brakujący indeks. (OK, ten ostatni nie jest tak naprawdę, do czego należy stosować testy jednostkowe, ale to nie boli).
Załaduj kopię produkcyjnej bazy danych i przetestuj ją. Problem polega na tym, że w danym momencie możesz nie mieć pojęcia, co znajduje się w produkcyjnej bazie danych; Twoje testy mogą wymagać przepisania, jeśli dane zmienią się z czasem.
Niektóre osoby zauważyły, że obie te strategie opierają się na konkretnych danych, a test jednostkowy powinien przetestować tylko funkcjonalność. W tym celu widziałem sugerowane:
- Użyj fałszywego serwera bazy danych i sprawdź tylko, czy ORM wysyła poprawne zapytania w odpowiedzi na dane wywołanie metody.
Jakie strategie zastosowałeś do testowania aplikacji opartych na bazie danych, jeśli takie istnieją? Co działało najlepiej dla Ciebie?
źródło
Odpowiedzi:
Właściwie zastosowałem twoje pierwsze podejście z pewnym powodzeniem, ale w nieco inny sposób, który moim zdaniem rozwiązałby niektóre z twoich problemów:
Zachowaj cały schemat i skrypty do tworzenia go pod kontrolą źródła, aby każdy mógł utworzyć bieżący schemat bazy danych po wymeldowaniu. Ponadto przechowuj przykładowe dane w plikach danych, które są ładowane przez część procesu kompilacji. Gdy odkryjesz dane, które powodują błędy, dodaj je do przykładowych danych, aby sprawdzić, czy błędy nie pojawiają się ponownie.
Użyj serwera ciągłej integracji, aby zbudować schemat bazy danych, załadować przykładowe dane i uruchomić testy. W ten sposób synchronizujemy naszą testową bazę danych (odbudowując ją przy każdym uruchomieniu testowym). Chociaż wymaga to, aby serwer CI miał dostęp i własność własnej dedykowanej instancji bazy danych, mówię, że zbudowanie naszego schematu db 3 razy dziennie znacznie pomogło znaleźć błędy, które prawdopodobnie nie zostałyby wykryte tuż przed dostarczeniem (jeśli nie później) ). Nie mogę powiedzieć, że odbudowuję schemat przed każdym zatwierdzeniem. Czy ktoś? Dzięki takiemu podejściu nie będziesz musiał (no może powinniśmy, ale nie jest to wielka sprawa, jeśli ktoś zapomni).
W mojej grupie wprowadzanie danych przez użytkownika odbywa się na poziomie aplikacji (nie db), więc jest to testowane za pomocą standardowych testów jednostkowych.
Ładowanie kopii produkcyjnej bazy danych:
takie podejście zastosowano przy mojej ostatniej pracy. To była ogromna bolesna przyczyna kilku problemów:
Mocking Database Server:
Robimy to również w mojej obecnej pracy. Po każdym zatwierdzeniu wykonujemy testy jednostkowe kodu aplikacji, do którego zostały wstrzyknięte pozorne akcesory db. Następnie trzy razy dziennie wykonujemy pełną kompilację db opisaną powyżej. Zdecydowanie polecam oba podejścia.
źródło
Zawsze uruchamiam testy przeciwko DB w pamięci (HSQLDB lub Derby) z następujących powodów:
DB w pamięci jest ładowany świeżymi danymi po rozpoczęciu testów, a po większości testów wywołuję ROLLBACK, aby zachować stabilność. ZAWSZE utrzymuj dane w testowej bazie danych stabilne! Jeśli dane cały czas się zmieniają, nie można testować.
Dane są ładowane z SQL, szablonu DB lub zrzutu / kopii zapasowej. Wolę zrzuty, jeśli są w czytelnym formacie, ponieważ mogę umieścić je w VCS. Jeśli to nie działa, używam pliku CSV lub XML. Jeśli muszę załadować ogromne ilości danych ... nie robię tego. Nigdy nie musisz ładować ogromnych ilości danych :) Nie do testów jednostkowych. Testy wydajności to kolejny problem i obowiązują inne zasady.
źródło
Zadaję to pytanie od dłuższego czasu, ale myślę, że nie ma na to srebrnej kuli.
Obecnie robię drwiny z obiektów DAO i przechowuję w pamięci dobrą kolekcję obiektów reprezentujących ciekawe przypadki danych, które mogłyby istnieć w bazie danych.
Główny problem, jaki dostrzegam w tym podejściu, polega na tym, że pokrywasz tylko kod, który wchodzi w interakcję z twoją warstwą DAO, ale nigdy nie testujesz samego DAO, i z mojego doświadczenia wynika, że na tej warstwie również występuje wiele błędów. Prowadzę również kilka testów jednostkowych, które działają na bazie danych (w celu użycia TDD lub szybkiego testowania lokalnego), ale testy te nigdy nie są uruchamiane na moim serwerze ciągłej integracji, ponieważ nie przechowujemy bazy danych w tym celu, a ja zdaniem testy uruchomione na serwerze CI powinny być niezależne.
Innym podejściem, które uważam za bardzo interesujące, ale nie zawsze warte uwagi, ponieważ jest trochę czasochłonne, jest stworzenie tego samego schematu, którego używasz do produkcji we wbudowanej bazie danych, która działa tylko w ramach testów jednostkowych.
Chociaż nie ma wątpliwości, że to podejście poprawia zasięg, ma kilka wad, ponieważ musisz być jak najbliżej ANSI SQL, aby działał zarówno z bieżącym DBMS, jak i wbudowanym zamiennikiem.
Bez względu na to, co Twoim zdaniem jest bardziej odpowiednie dla Twojego kodu, istnieje kilka projektów, które mogą to ułatwić, na przykład DbUnit .
źródło
Nawet jeśli istnieją narzędzia, które pozwalają na mock bazy danych w taki czy inny sposób (np jOOQ „s
MockConnection
, które można zobaczyć w tej odpowiedzi - Zastrzeżone, pracuję dla dostawcy jOOQ za), I radzę nie wyśmiewać większych baz danych z kompleksem zapytania.Nawet jeśli chcesz tylko przetestować integrację ORM, pamiętaj, że ORM wysyła do bazy danych bardzo złożoną serię zapytań, które mogą się różnić
Wyśmiewanie się z wszystkiego, aby uzyskać sensowne dane pozorne, jest dość trudne, chyba że w rzeczywistości buduje się małą bazę danych wewnątrz tego modelu, która interpretuje przesyłane instrukcje SQL. Powiedziawszy to, skorzystaj ze znanej bazy danych testów integracji, którą możesz łatwo zresetować za pomocą dobrze znanych danych, na podstawie której możesz uruchomić testy integracji.
źródło
Używam pierwszego (uruchamianie kodu na testowej bazie danych). Jedynym istotnym problemem, jaki widzę w tym podejściu, jest możliwość zsynchronizowania schematów, z którą mam do czynienia, utrzymując numer wersji w mojej bazie danych i wprowadzając wszystkie zmiany schematu za pomocą skryptu, który stosuje zmiany dla każdego przyrostu wersji.
Najpierw wprowadzam również wszystkie zmiany (w tym schemat bazy danych) w moim środowisku testowym, więc jest odwrotnie: po przejściu wszystkich testów zastosuj aktualizacje schematu na hoście produkcyjnym. W systemie deweloperskim przechowuję również osobną parę baz danych testowania i aplikacji, dzięki czemu mogę sprawdzić, czy aktualizacja db działa poprawnie, zanim dotknę prawdziwych pól produkcyjnych.
źródło
Korzystam z pierwszego podejścia, ale nieco innego, co pozwala rozwiązać wspomniane problemy.
Wszystko, co jest potrzebne do uruchomienia testów dla DAO, znajduje się pod kontrolą źródła. Zawiera schemat i skrypty do tworzenia bazy danych (doker jest do tego bardzo dobry). Jeśli można użyć wbudowanego DB - używam go do szybkości.
Ważną różnicą w stosunku do innych opisanych podejść jest to, że dane wymagane do testu nie są ładowane ze skryptów SQL lub plików XML. Wszystko (z wyjątkiem niektórych danych słownikowych, które są faktycznie stałe) jest tworzone przez aplikację przy użyciu funkcji / klas narzędzi.
Głównym celem jest wykorzystanie danych do testów
Zasadniczo oznacza to, że narzędzia te pozwalają deklaratywnie określać tylko rzeczy niezbędne dla samego testu w teście i pomijać rzeczy nieistotne.
Aby dać pojęcie o tym, co to znaczy w praktyce, rozważ test dla DAO, który działa z napisami
Comment
s doPost
s napisanymi przezAuthors
. Aby przetestować operacje CRUD dla takiego DAO, niektóre dane powinny zostać utworzone w DB. Test wyglądałby następująco:Ma to kilka zalet w porównaniu ze skryptami SQL lub plikami XML z danymi testowymi:
Wycofywanie kontra zatwierdzanie
Uważam, że wygodniej jest, gdy testy wykonują zatwierdzenie po ich wykonaniu. Po pierwsze, niektóre efekty (na przykład
DEFERRED CONSTRAINTS
) nie można sprawdzić, jeśli nigdy się nie wydarzy. Po drugie, gdy test się nie powiedzie, dane można sprawdzić w bazie danych, ponieważ nie są one przywracane przez wycofanie.Oczywiście ma to wadę, że test może spowodować uszkodzenie danych, co doprowadzi do niepowodzenia w innych testach. Aby sobie z tym poradzić, próbuję wyizolować testy. W powyższym przykładzie każdy test może tworzyć nowe,
Author
a wszystkie inne podmioty są z nim powiązane, więc kolizje są rzadkie. Aby poradzić sobie z pozostałymi niezmiennikami, które mogą być potencjalnie zepsute, ale nie mogą być wyrażone jako ograniczenie poziomu DB, używam niektórych kontroli programowych pod kątem błędnych warunków, które mogą być uruchamiane po każdym pojedynczym teście (i są uruchamiane w CI, ale zwykle są wyłączane lokalnie dla wydajności powody).źródło
PostBuilder.post()
. Generuje pewne wartości dla wszystkich obowiązkowych atrybutów postu. Nie jest to potrzebne w kodzie produkcyjnym.W przypadku projektu opartego na JDBC (bezpośrednio lub pośrednio, np. JPA, EJB, ...) możesz wykonać makietę nie całej bazy danych (w takim przypadku lepiej byłoby użyć testowej bazy danych na prawdziwym RDBMS), ale tylko makietę na poziomie JDBC .
Zaletą jest abstrakcja, która przychodzi w ten sposób, ponieważ dane JDBC (zestaw wyników, liczba aktualizacji, ostrzeżenie, ...) są takie same bez względu na backend: twoja baza danych prod, testowa baza danych lub tylko niektóre dane makiety dostarczone dla każdego testu walizka.
Po skonfigurowaniu połączenia JDBC dla każdego przypadku nie ma potrzeby zarządzania testową bazą danych (czyszczenie, tylko jeden test na raz, przeładowywanie urządzeń, ...). Każde połączenie makiety jest izolowane i nie ma potrzeby czyszczenia. W każdym przypadku testowym udostępniane są tylko minimalne wymagane urządzenia, aby wyodrębnić wymianę JDBC, co pomaga uniknąć złożoności zarządzania całym testowym plikiem db.
Acolyte to moja platforma, która zawiera sterownik JDBC i narzędzie do tego rodzaju makiety: http://acolyte.eu.org .
źródło