Jaka jest najlepsza strategia testowania jednostkowego aplikacji bazodanowych?

Pracuję z wieloma aplikacjami webowymi, które są napędzane przez bazy danych o różnej złożoności w backendzie. Zazwyczaj istnieje warstwa ORM oddzielona od logiki biznesowej i prezentacji. To sprawia, że testowanie jednostkowe logiki biznesowej jest dość proste; rzeczy mogą być zaimplementowane w dyskretnych modułach, a wszelkie dane potrzebne do testu mogą być sfałszowane przez obiekt mocking.

Ale testowanie ORM i samej bazy danych zawsze było obarczone problemami i kompromisy.

Przez lata wypróbowałem kilka strategii, z których żadna w pełni mnie nie zadowoliła.

  • Załaduj testową bazę danych znanymi danymi. Uruchom testy z ORM i potwierdź, że właściwe dane wracają. Wadą jest to, że test DB musi nadążać za wszelkimi zmianami schematu w bazie danych aplikacji i może się wymknąć synchronizacji. Opiera się również na sztucznych danych i może nie ujawniać błędów, które występują z powodu głupiego wprowadzania przez użytkownika. Wreszcie, jeśli test baza danych jest mała, nie ujawni nieefektywności, takich jak brakujący indeks. (OK, ten ostatni nie jest tak naprawdę tym, do czego powinny być używane testy jednostkowe, ale to nie boli.)

  • Załaduj kopię bazy danych produkcji i przetestuj ją. Problem polega na tym, że możesz nie mieć pojęcia, co znajduje się w DB produkcji w danym momencie; twoje testy mogą wymagać przepisania, jeśli dane zmienią się w czasie.

Niektórzy zwracali uwagę, że obie te strategie polegają na dane szczegółowe, a test jednostkowy powinien testować tylko funkcjonalność. W tym celu widziałem propozycje:

  • użyj makiety serwera bazy danych i sprawdź tylko, czy ORM wysyła poprawne zapytania w odpowiedzi na wywołanie danej metody.

Jakich strategii użyłeś do testowania aplikacji opartych na bazach danych, jeśli w ogóle? Co jest dla ciebie najlepsze?

Author: friedo, 2008-09-28

7 answers

Użyłem Twojego pierwszego podejścia z dość dużym sukcesem, ale w nieco inny sposób, który moim zdaniem rozwiąże niektóre z twoich problemów:

  1. Zachowaj cały schemat i skrypty do jego tworzenia w sterowaniu źródłowym, aby każdy mógł utworzyć bieżący schemat bazy danych po sprawdzeniu. Ponadto przechowuj przykładowe dane w plikach danych, które są ładowane przez część procesu budowania. Gdy odkryjesz dane, które powodują błędy, dodaj je do przykładowych danych, aby sprawdzić, czy błędy nie pojawiaj się ponownie.

  2. Użyj serwera ciągłej integracji, aby zbudować schemat bazy danych, załadować przykładowe dane i uruchomić testy. W ten sposób utrzymujemy synchronizację bazy testowej (przebudowujemy 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 mając nasz schemat db zbudowany 3 razy dziennie znacznie pomógł znaleźć błędy, które prawdopodobnie nie zostały znalezione przed tuż przed dostawą (Jeśli nie później). Nie mogę powiedzieć, że odbudowuję schemat przed każdym commitem. Czy ktokolwiek? Przy takim podejściu nie będziesz musiał(no może powinniśmy, ale to nie jest wielka sprawa, jeśli ktoś zapomni).

  3. Dla mojej grupy 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 Bazy Danych Produkcji:
Takie podejście było stosowane w mojej ostatniej pracy. To był ogromny ból przyczyną kilku zagadnienia:

  1. kopia będzie nieaktualna od wersji produkcyjnej
  2. zmiany nastąpiłyby w schemacie kopii i nie byłyby propagowane w systemach produkcyjnych. W tym momencie mielibyśmy rozbieżne Schematy. To nie jest zabawne.

Serwer Bazy Danych:
Robimy to również w mojej obecnej pracy. Po każdym commicie wykonujemy testy jednostkowe z kodem aplikacji, które mają zaimplementowane mock dB accessors. Następnie trzy razy dziennie wykonujemy pełny build db opisane powyżej. Zdecydowanie polecam oba podejścia.

 160
Author: Mark Roddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-16 16:19:58

Zawsze przeprowadzam testy na dB w pamięci (HSQLDB lub Derby) z tych powodów:

  • to sprawia, że myślisz, które dane zachować w testowym DB i dlaczego. Po prostu wciągnięcie dB produkcji do systemu testowego przekłada się na " nie mam pojęcia, co robię i dlaczego, a jeśli coś się zepsuje, to nie byłem ja!!" ;)
  • zapewnia, że baza danych może być odtworzona bez większego wysiłku w nowym miejscu (na przykład, gdy musimy replikować błąd z produkcji)
  • to pomaga ogromnie z jakością plików DDL.

DB w pamięci jest ładowany świeżymi danymi po rozpoczęciu testów i po większości testów, wywołuję ROLLBACK, aby utrzymać stabilność. zawsze przechowuj dane w testowym DB stabilnie! Jeśli DANE zmieniają się cały czas, nie można przetestować.

Dane są ładowane z SQL, DB szablonu lub zrzutu / kopii zapasowej. Wolę zrzuty, jeśli są w czytelnym formacie, ponieważ mogę je umieścić w VCS. Jeśli to nie zadziała, używam pliku CSV lub XML. Jeśli mam aby załadować ogromne ilości danych ... Ja nie. nigdy nie trzeba ładować ogromnych ilości danych :) nie do testów jednostkowych. Testy wydajnościowe to kolejna kwestia i obowiązują inne zasady.

 59
Author: Aaron Digulla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-24 15:34:50

Zadaję to pytanie od dłuższego czasu, ale myślę, że nie ma na to srebrnej kuli.

To, co obecnie robię, to wyśmiewanie obiektów DAO i utrzymywanie w pamięci reprezentacji dobrego zbioru obiektów, które reprezentują interesujące przypadki danych, które mogą żyć w bazie danych.

Głównym problemem, jaki widzę w tym podejściu jest to, że przykrywasz tylko kod, który współdziała z Twoją warstwą DAO, ale nigdy nie testujesz samego DAO, a z mojego doświadczenia widzę że wiele błędów zdarza się również na tej warstwie. Przechowuję również kilka testów jednostkowych, które działają przeciwko bazie danych (ze względu na używanie TDD lub quick testing lokalnie), ale te testy nigdy nie są uruchamiane na moim serwerze continuous integration, ponieważ nie przechowujemy bazy danych w tym celu i myślę, że testy uruchamiane na serwerze CI powinny być samodzielne.

Inne podejście uważam za bardzo interesujące, ale nie zawsze warto, ponieważ jest trochę czasochłonne, jest stworzenie tego samego schematu, którego używasz do produkcja na wbudowanej bazie danych, która po prostu działa w ramach testów jednostkowych.

Chociaż nie ma wątpliwości, że takie podejście poprawia zasięg, istnieje kilka wad, ponieważ musisz być jak najbliżej ANSI SQL, aby działał zarówno z bieżącym DBMS, jak i osadzonym zamiennikiem.

Bez względu na to, co Twoim zdaniem jest bardziej istotne dla Twojego kodu, istnieje kilka projektów, które mogą to ułatwić, takich jak DbUnit.

 14
Author: kolrie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-28 03:55:29

Nawet jeśli istnieją narzędzia, które pozwalają na makietę bazy danych w taki czy inny sposób (np. jOOQ ' SMockConnection, co widać w ta odpowiedź - disclaimer, pracuję dla dostawcy jOOQ), radzę a nie szydzić większe bazy danych ze złożonymi zapytaniami.

Nawet jeśli chcesz po prostu przetestować swój ORM, uważaj, że ORM wysyła do twojej bazy danych bardzo złożoną serię zapytań, które mogą się różnić w

  • składnia
  • złożoność
  • order (!)

Wyśmiewanie tego wszystkiego w celu wytworzenia sensownych fałszywych danych jest dość trudne, chyba że faktycznie budujesz małą bazę danych wewnątrz makiety, która interpretuje przesyłane instrukcje SQL. Mimo to skorzystaj ze znanej bazy integration-test, którą możesz łatwo zresetować za pomocą znanych danych, na podstawie których możesz uruchomić testy integracyjne.

 13
Author: Lukas Eder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:23

Używam pierwszego (uruchamiam kod na testowej bazie danych). Jedynym merytorycznym problemem widzę cię podnosząc z tym podejściem jest możliwość schematów coraz z synchronizacji, które mam do czynienia przez utrzymanie numeru wersji w mojej bazie danych i Dokonywanie wszystkich zmian schematu za pomocą skryptu, który stosuje zmiany dla każdej wersji przyrost.

Wprowadzam również wszystkie zmiany (w tym schemat bazy danych) w moim środowisku testowym, więc kończy się to na odwrót: w końcu testy przechodzą, zastosuj aktualizacje schematu do hosta produkcyjnego. Przechowuję również oddzielną parę baz danych testing vs. application w moim systemie deweloperskim, dzięki czemu mogę tam sprawdzić, czy aktualizacja db działa poprawnie przed dotknięciem prawdziwego pola produkcyjnego(es).

 5
Author: Dave Sherohman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-24 17:06:47

Używam pierwszego podejścia, ale nieco innego, które pozwala rozwiązać problemy, o których wspomniałeś.

Wszystko, co jest potrzebne do uruchomienia testów DAOs, znajduje się pod kontrolą źródła. Zawiera schemat i skrypty do tworzenia bazy danych (docker jest do tego bardzo dobry). Jeśli można użyć wbudowanego DB - używam go dla szybkości.

Istotna różnica w stosunku do innych opisanych metod polega na tym, że dane wymagane do testów 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 tworzony przez aplikację za pomocą funkcji/klas użytkowych.

Głównym celem jest wykorzystanie danych przez test

  1. bardzo blisko testu
  2. [15]} explicit (korzystanie z plików SQL dla danych sprawia, że bardzo problematyczne jest sprawdzenie, jaki kawałek danych jest używany przez jaki test)
  3. odizoluj testy od niepowiązanych zmian.

W zasadzie oznacza to, że narzędzia te pozwalają deklaratywnie określić tylko rzeczy istotne dla testu w samym teście i pomiń nieistotne rzeczy.

Aby dać jakieś pojęcie o tym, co to znaczy w praktyce, rozważ test dla niektórych DAO, który działa z Comment s do Post S napisany przez Authors. W celu przetestowania operacji CRUD dla takiego DAO niektóre dane powinny być tworzone w DB. Test wyglądałby następująco:

@Test
public void savedCommentCanBeRead() {
    // Builder is needed to declaratively specify the entity with all attributes relevant
    // for this specific test
    // Missing attributes are generated with reasonable values
    // factory's responsibility is to create entity (and all entities required by it
    //  in our example Author) in the DB
    Post post = factory.create(PostBuilder.post());

    Comment comment = CommentBuilder.comment().forPost(post).build();

    sut.save(comment);

    Comment savedComment = sut.get(comment.getId());

    // this checks fields that are directly stored
    assertThat(saveComment, fieldwiseEqualTo(comment));
    // if there are some fields that are generated during save check them separately
    assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));        
}

Ma to kilka zalet w stosunku do skryptów SQL lub plików XML z danymi testowymi:

  1. utrzymanie kodu jest znacznie łatwiejsze (dodanie obowiązkowej kolumny dla przykład w niektórych obiektach, do których odwołuje się wiele testów, takich jak autor, nie wymaga zmiany wielu plików / rekordów, ale tylko zmiany w konstruktorze i / lub fabryce)
  2. dane wymagane przez konkretny test są opisane w samym teście, a nie w jakimś innym pliku. Ta bliskość jest bardzo ważna dla zrozumiałości testu.

Rollback vs Commit

Wydaje mi się wygodniejsze, aby testy commit były wykonywane. Po pierwsze, niektóre efekty (na przykład DEFERRED CONSTRAINTS) nie można sprawdzić, jeśli commit nigdy się nie wydarzy. Po drugie, gdy test nie powiedzie się, dane mogą być badane w DB, ponieważ nie są przywracane przez wycofanie.

Przyczyny ma to minusy, że test może produkować uszkodzone dane, a to doprowadzi do błędów w innych testach. Aby sobie z tym poradzić, staram się wyizolować testy. W powyższym przykładzie każdy test może tworzyć nowe Author, a wszystkie inne byty są z nim związane, więc kolizje są rzadkie. Do czynienia z pozostałymi niezmiennikami, które mogą być potencjalnie uszkodzony, ale nie może być wyrażony jako ograniczenie poziomu DB używam kilku programowych sprawdzeń błędnych warunków, które mogą być uruchamiane po każdym pojedynczym teście(i są uruchamiane w CI, ale zwykle wyłączane lokalnie ze względu na wydajność).

 3
Author: Roman Konoval,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-06 12:32:27

Dla projektu opartego na JDBC (bezpośrednio lub pośrednio, np. JPA, EJB,...) moĹźna mockup nie caĹ 'Ä ... bazÄ ™ danych (w takim przypadku lepiej byĹ' o uĹźyÄ ‡ test db na prawdziwym RDBMS), a jedynie mockup na poziomie JDBC.

Zaletą jest abstrakcja, która przychodzi w ten sposób, jako dane JDBC (zestaw wyników, liczba aktualizacji, Ostrzeżenie, ...) są takie same, niezależnie od tego, co jest backendem: Twój prod db, test db lub po prostu niektóre dane makiety dostarczone dla każdego przypadku testowego.

Z połączeniem JDBC dla każdego przypadek nie ma potrzeby zarządzania test db (cleanup, tylko jeden test na raz, Reload oprawy,...). Każde połączenie makiety jest odizolowane i nie ma potrzeby czyszczenia. Tylko minimalne wymagane urządzenia są dostarczane w każdym przypadku testowym do makiety JDBC exchange, które pomagają uniknąć złożoności zarządzania całym testowym db.

Acolyte to mój framework, który zawiera sterownik JDBC i narzędzie do tego typu makiet: http://acolyte.eu.org .

 2
Author: cchantep,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-28 21:07:36