Testy jednostkowe i bazy danych: w którym momencie faktycznie łączę się z bazą danych?

37

Istnieją odpowiedzi na pytanie, w jaki sposób klasy testowe łączą się z bazą danych, np. „Powinny połączyć klasy testowe usługi ...” i „Testowanie jednostkowe - aplikacja połączona z bazą danych” .

Krótko mówiąc, załóżmy, że masz klasę A, która musi połączyć się z bazą danych. Zamiast pozwolić A faktycznie się połączyć, udostępniasz interfejsowi A, którego A może użyć do połączenia. Do testowania implementujesz ten interfejs z pewnymi rzeczami - oczywiście bez podłączania. Jeśli klasa B tworzy instancję A, musi przekazać „rzeczywiste” połączenie z bazą danych do A. Ale to oznacza, że ​​B otwiera połączenie z bazą danych. Oznacza to, że do testu B wstrzykujesz połączenie do B. Ale B jest tworzone w klasie C i tak dalej.

Więc w którym momencie muszę powiedzieć „tutaj pobieram dane z bazy danych i nie będę pisać testu jednostkowego dla tego fragmentu kodu”?

Innymi słowy: Gdzieś w kodzie w jakiejś klasy I muszą zadzwonić sqlDB.connect()lub coś podobnego. Jak przetestować tę klasę?

Czy to samo dotyczy kodu, który musi obsługiwać GUI lub system plików?


Chcę zrobić test jednostkowy. Jakikolwiek inny test nie ma związku z moim pytaniem. Wiem, że przetestuję tylko jedną klasę (zgadzam się z tobą Kilian). Teraz niektóre klasy muszą połączyć się z bazą danych. Jeśli chcę przetestować tę klasę i zapytać „Jak to zrobić”, wielu mówi: „Użyj wstrzykiwania zależności!” Ale to tylko przenosi problem na inną klasę, prawda? Pytam więc, jak mam przetestować klasę, która naprawdę ustanawia połączenie?

Dodatkowe pytanie: niektóre odpowiedzi tutaj sprowadzają się do „Użyj fałszywych obiektów!” Co to znaczy? Kpię z klas, od których zależy testowana klasa. Czy mam teraz wyśmiewać testowaną klasę i faktycznie testować próbę (która jest bliska idei użycia metod szablonów, patrz poniżej)?

TobiMcNamobi
źródło
Czy testujesz połączenie z bazą danych? Czy dopuszczalne byłoby utworzenie tymczasowej bazy danych w pamięci (takiej jak derby )?
@MichaelT Wciąż muszę zastąpić tymczasową bazę danych w pamięci prawdziwą bazą danych. Gdzie? Kiedy? Jak to jest testowane jednostkowo? Czy może nie jest testowane jednostkowo ten kod?
TobiMcNamobi
3
Baza danych nie ma nic do „testu jednostkowego”. Jest utrzymywany przez innych ludzi, a jeśli byłby to błąd, musisz pozwolić mu to naprawić, niż zrobić to sam. Jedyną rzeczą, która powinna różnić się między faktycznym wykorzystaniem twojej klasy a użyciem podczas testów, powinny być parametry połączenia z bazą danych. Jest mało prawdopodobne, że kod odczytu pliku właściwości, mechanizm wstrzykiwania Springa lub cokolwiek, czego używasz do splotu aplikacji, jest zepsuty (a jeśli tak, to nie możesz go naprawić, patrz wyżej) - więc uważam, że jest to dopuszczalne nie testować tej funkcji hydraulicznej.
Kilian Foth
2
@KilianFoth, który jest wyłącznie związany ze środowiskiem pracy i rolami pracowników. To naprawdę nie ma nic wspólnego z pytaniem. Co zrobić, jeśli nie ma jednej osoby odpowiedzialnej za bazę danych?
Reactgular
Niektóre frameworki umożliwiają wstrzykiwanie fałszywych obiektów do praktycznie wszystkiego, nawet do prywatnych i statycznych elementów. To znacznie ułatwia testowanie takich rzeczy, jak fałszywe połączenia db. Mockito + Powermock działa obecnie dla mnie (to Java, nie jestem pewien, nad czym pracujesz).
FrustratedWithFormsDesigner

Odpowiedzi:

21

Celem testu jednostkowego jest przetestowanie jednej klasy (w rzeczywistości powinno to zwykle przetestować jedną metodę ).

Oznacza to, że kiedy testujesz klasę A, wstrzykujesz do niej testową bazę danych - coś samodzielnie napisanego lub błyskawiczną bazę danych w pamięci, niezależnie od tego, co zrobi zadanie.

Jeśli jednak przetestujesz klasę B, która jest klientem A, zwykle kpisz z całego Aobiektu czymś innym, prawdopodobnie czymś, co wykonuje swoją pracę w prymitywny, wstępnie zaprogramowany sposób - bez użycia rzeczywistego Aobiektu i na pewno bez użycia danych base (chyba że Aprzekazuje całe połączenie bazy danych z powrotem do swojego rozmówcy - ale to tak okropne, że nie chcę o tym myśleć). Podobnie, pisząc test jednostkowy dla klasy C, która jest klientem B, wyśmiewałbyś coś, co pełni rolę B, i Acałkowicie o nim zapominasz .

Jeśli tego nie zrobisz, nie będzie to już test jednostkowy, ale test systemowy lub test integracji. Te są również bardzo ważne, ale zupełnie inny czajnik ryb. Po pierwsze, zwykle wymagają większego wysiłku, aby skonfigurować i uruchomić, nie jest praktyczne wymaganie przekazania ich jako warunku wstępnego zameldowania itp.

Kilian Foth
źródło
11

Wykonywanie testów jednostkowych dla połączenia z bazą danych jest całkowicie normalne i jest powszechną praktyką. Po prostu nie można stworzyć puristpodejścia, w którym wszystko w twoim systemie można wstrzykiwać w zależności.

Kluczem tutaj jest przetestowanie tymczasowej lub testowej bazy danych i jak najlżejszy proces uruchamiania w celu zbudowania testowej bazy danych.

Do testowania jednostkowego w CakePHP są tak zwane fixtures. Urządzenia to tymczasowe tabele bazy danych tworzone w locie na potrzeby testu jednostkowego. Urządzenie ma dogodne metody ich tworzenia. Mogą odtworzyć schemat z produkcyjnej bazy danych w testowej bazie danych lub zdefiniować schemat za pomocą prostej notacji.

Kluczem do sukcesu jest nie wdrożenie biznesowej bazy danych, ale skupienie się tylko na aspekcie testowanego kodu. Jeśli masz test jednostkowy, który weryfikuje, czy model danych odczytuje tylko opublikowane dokumenty, wówczas schemat tabeli dla tego testu powinien zawierać tylko pola wymagane przez ten kod. Nie musisz ponownie wdrażać całej bazy danych zarządzania treścią, aby przetestować ten kod.

Kilka dodatkowych odniesień.

http://en.wikipedia.org/wiki/Test_fixture

http://phpunit.de/manual/3.7/en/database.html

http://book.cakephp.org/2.0/en/development/testing.html#fixtures

Reactgular
źródło
28
Nie zgadzam się. Test wymagający połączenia z bazą danych nie jest testem jednostkowym, ponieważ test ze swej natury będzie miał skutki uboczne. Nie oznacza to, że nie możesz napisać testu automatycznego, ale taki test jest z definicji testem integracyjnym, ćwiczącym obszary twojego systemu poza bazą kodu.
KeithS
5
Nazwij mnie purystą, ale trzymam się zasady, że test jednostkowy nie powinien wykonywać żadnych działań, które opuszczą „piaskownicę” testowego środowiska wykonawczego. Nie powinny dotykać baz danych, systemów plików, gniazd sieciowych itp. Jest to z kilku powodów, między innymi z zależności testu od stanu zewnętrznego. Kolejnym jest występ; zestaw testów jednostkowych powinien działać szybko, a interfejs z tymi zewnętrznymi magazynami danych powolny test o rząd wielkości. W moim własnym rozwoju używam częściowych prób do testowania takich rzeczy, jak moje repozytoria, i mogę swobodnie definiować „krawędź” mojej piaskownicy.
KeithS
2
@gbjbaanb - Z początku brzmi dobrze, ale z mojego doświadczenia jest to bardzo niebezpieczne. Nawet w najlepiej zaprojektowanych pakietach testowych i strukturach kod przywracania tej transakcji może nie zostać wykonany. Jeśli test runner ulegnie awarii lub zostanie przerwany w teście, lub test wyrzuci SOE lub OOME, najlepszym rozwiązaniem jest zawieszenie połączenia i transakcja w DB, która zablokuje dotknięte tabele, dopóki połączenie nie zostanie przerwane. Sposoby zapobiegania problemom, takie jak używanie SQLite jako testowej bazy danych, mają swoje wady, na przykład fakt, że tak naprawdę nie ćwiczysz prawdziwej bazy danych.
KeithS
5
@KeithS Myślę, że debatujemy nad semantyką. Nie chodzi o to, jaka jest definicja testu jednostkowego czy testu integracyjnego. Używam urządzeń do testowania kodu w zależności od połączenia z bazą danych. Jeśli to test integracyjny, to nie mam nic przeciwko. Muszę wiedzieć, że test mija. Nie zależało mi na zależnościach, wydajności lub ryzyku. Nie będę wiedział, czy ten kod działa, dopóki ten test nie przejdzie pomyślnie. W przypadku większości testów nie ma zależności, ale w przypadku tych, których istnieją, zależności tych nie można rozdzielić. Łatwo powiedzieć, że powinni, ale po prostu nie mogą.
Reactgular
4
Myślę, że my też. Używam „środowiska testowania jednostek” (NUnit) również do moich testów integracyjnych, ale upewniam się, że segreguję te dwie kategorie testów (często w oddzielnych bibliotekach). Chciałem nadmienić, że twój pakiet testów jednostkowych, ten, który uruchamiasz kilka razy dziennie przed każdym odprawą, postępując zgodnie z iteracyjną metodologią refaktora czerwono-zielonego, powinien być całkowicie izolowalny, abyś mógł uruchomić testy te kilka razy dziennie bez nadepnięcia na palcach współpracowników.
KeithS
4

Gdzieś w twojej bazie kodu jest wiersz kodu, który wykonuje rzeczywistą akcję połączenia ze zdalną bazą danych. Ten wiersz kodu jest 9 razy na 10 wywołaniem „wbudowanej” metody dostarczanej przez biblioteki środowiska wykonawczego specyficzne dla Twojego języka i środowiska. Jako taki nie jest to „twój” kod, więc nie musisz go testować; na potrzeby testu jednostkowego możesz mieć pewność, że to wywołanie metody będzie działać poprawnie. To, co możesz i powinieneś nadal testować w swoim pakiecie testów jednostkowych, to takie, jak upewnienie się, że parametry, które będą używane dla tego wywołania, są zgodne z oczekiwaniami, takie jak upewnienie się, że parametry połączenia są poprawne, instrukcja SQL lub nazwa procedury składowanej.

Jest to jeden z celów stojących za ograniczeniem, że testy jednostkowe nie powinny pozostawiać ich środowiska uruchomieniowego „piaskownicy” i być zależne od stanu zewnętrznego. To właściwie całkiem praktyczne; celem testu jednostkowego jest sprawdzenie, czy kod, który napisałeś (lub masz zamiar napisać, w TDD) zachowuje się tak, jak się spodziewałeś. Kod, którego nie napisałeś, taki jak biblioteka, której używasz do wykonywania operacji na bazie danych, nie powinien wchodzić w zakres żadnego testu jednostkowego, z bardzo prostego powodu, że go nie napisałeś.

W pakiecie testów integracji ograniczenia te są złagodzone. Teraz już mogęprojektuj testy, które dotykają bazy danych, aby upewnić się, że napisany kod działa dobrze z kodem, którego nie zrobiłeś. Te dwa pakiety testowe powinny jednak pozostać posegregowane, ponieważ pakiet testów jednostkowych jest bardziej skuteczny, im szybciej działa (dzięki czemu można szybko sprawdzić, czy wszystkie twierdzenia deweloperów dotyczące ich kodu są nadal aktualne), a prawie z definicji test integracji jest wolniejszy o rząd wielkości ze względu na dodatkowe zależności od zasobów zewnętrznych. Pozwól build-botowi obsługiwać uruchamianie pełnego pakietu integracyjnego co kilka godzin, wykonując testy blokujące zasoby zewnętrzne, aby programiści nie stawali sobie na nogi, uruchamiając te same testy lokalnie. A jeśli kompilacja się zepsuje, co z tego? Dużo większą wagę przywiązuje się do zapewnienia, że ​​build-bot nigdy nie zawiedzie kompilacji, niż prawdopodobnie powinno być.


To, jak ściśle możesz się do tego zastosować, zależy od dokładnej strategii łączenia się z bazą danych i wysyłania do niej zapytań. W wielu przypadkach, w których musisz używać szkieletowej struktury dostępu do danych, na przykład obiektów SqlConnection i SqlStatement ADO.NET, cała opracowana przez Ciebie metoda może składać się z wbudowanych wywołań metod i innego kodu zależnego od posiadania połączenie z bazą danych, dlatego najlepszym rozwiązaniem w tej sytuacji jest wyśmiewanie się z całej funkcji i zaufanie do pakietów testowych integracji. Zależy to również od tego, jak chętnie zaprojektujesz swoje klasy, aby umożliwić zastąpienie określonych wierszy kodu do celów testowych (takich jak sugestia Tobiego dotycząca wzorca metody szablonów, co jest dobre, ponieważ pozwala na „częściowe kpiny”

Jeśli model trwałości danych opiera się na kodzie w warstwie danych (takim jak wyzwalacze, przechowywane procy itp.), To po prostu nie ma innego sposobu na wykonanie kodu, który sam piszesz, niż opracowanie testów, które albo żyją wewnątrz warstwy danych, albo przekraczają granica między środowiskiem uruchomieniowym aplikacji a systemem DBMS. Z tego powodu purysta powiedziałby, że tego wzoru należy unikać na rzecz czegoś w rodzaju ORM. Nie sądzę, żebym posunął się tak daleko; nawet w dobie zapytań zintegrowanych z językiem i innych sprawdzanych przez kompilator operacji trwałości zależnych od domeny widzę wartość w blokowaniu bazy danych tylko do operacji ujawnionych za pomocą procedury składowanej, i oczywiście takie procedury przechowywane muszą być zweryfikowane za pomocą zautomatyzowanego testy. Ale takie testy nie są testami jednostkowymi . Są integracją testy.

Jeśli masz problem z tym rozróżnieniem, zwykle opiera się ono na dużym znaczeniu przywiązywanym do pełnego „pokrycia kodu”, czyli „pokrycia testu jednostkowego”. Chcesz się upewnić, że każdy wiersz kodu jest objęty testem jednostkowym. Szlachetny cel na pierwszy rzut oka, ale mówię, że to miazga; ta mentalność nadaje się do anty-wzorów wykraczających daleko poza ten konkretny przypadek, takich jak pisanie testów bez asercji, które wykonują, ale nie ćwicząTwój kod. Tego rodzaju przebiegi końcowe wyłącznie ze względu na numery ubezpieczenia są bardziej szkodliwe niż złagodzenie minimalnego zakresu ubezpieczenia. Jeśli chcesz mieć pewność, że każda linia bazy kodu jest wykonywana przez jakiś automatyczny test, to jest łatwe; podczas obliczania wskaźników zasięgu kodu należy uwzględnić testy integracyjne. Możesz nawet pójść o krok dalej i odizolować te sporne testy „Itino” („Integracja tylko w nazwie”), a między pakietem testów jednostkowych a tą podkategorią testów integracyjnych (które powinny nadal działać dość szybko) powinieneś dostać cholerę prawie blisko pełnego zasięgu.

KeithS
źródło
2

Testy jednostkowe nigdy nie powinny łączyć się z bazą danych. Z definicji powinni testować każdą jednostkę kodu (metodę) w całkowitej izolacji od reszty systemu. Jeśli nie, to nie są testem jednostkowym.

Oprócz semantyki istnieje wiele powodów, dla których jest to korzystne:

  • Testy działają szybciej o rząd wielkości
  • Pętla sprzężenia zwrotnego staje się natychmiastowa (na przykład sprzężenie zwrotne <1 s dla TDD)
  • Testy można uruchamiać równolegle dla systemów kompilacji / wdrażania
  • Testy nie wymagają uruchamiania bazy danych (znacznie kompilują, a przynajmniej przyspieszają)

Testy jednostkowe są sposobem na sprawdzenie pracy. Powinny one nakreślić wszystkie scenariusze dla danej metody, co zazwyczaj oznacza wszystkie różne ścieżki danej metody. To twoja specyfikacja, którą budujesz, podobna do księgowości z podwójnym wpisem.

Opisujesz inny typ testu automatycznego: test integracji. Chociaż są one również bardzo ważne, idealnie będzie ich o wiele mniej. Powinny sprawdzić, czy grupa jednostek prawidłowo się ze sobą integruje.

Jak więc testować rzeczy z dostępem do bazy danych? Cały kod dostępu do danych powinien znajdować się w określonej warstwie, aby kod aplikacji mógł wchodzić w interakcję z usługami możliwymi do naśladowania zamiast z rzeczywistą bazą danych. Nie powinno obchodzić się, czy te usługi są wspierane przez dowolny typ bazy danych SQL, dane testowe w pamięci, a nawet dane zdalnej usługi internetowej. To nie ich troska.

Idealnie (i to jest bardzo subiektywne), chcesz, aby większość kodu była objęta testami jednostkowymi. Daje to pewność, że każdy element działa niezależnie. Po zbudowaniu elementów musisz je połączyć. Przykład - kiedy hashuję hasło użytkownika, powinienem uzyskać dokładnie ten wynik.

Powiedzmy, że każdy element składa się z około 5 klas - chciałbyś przetestować wszystkie punkty awarii w nich zawarte. Oznacza to znacznie mniej testów, aby upewnić się, że wszystko jest prawidłowo okablowane. Przykład - test możesz znaleźć użytkownika z bazy danych, podając nazwę użytkownika / hasło.

Wreszcie, chcesz, aby niektóre testy akceptacyjne rzeczywiście zapewniły osiągnięcie celów biznesowych. Jest ich jeszcze mniej; mogą upewnić się, że aplikacja działa i robi to, co została stworzona. Przykład - biorąc pod uwagę te dane testowe, powinienem móc się zalogować.

Pomyśl o tych trzech rodzajach testów jak o piramidzie. Potrzebujesz wielu testów jednostkowych, aby wesprzeć wszystko, a następnie stamtąd wspinasz się.

Adrian Schneider
źródło
1

Metoda szablonowa może pomóc.

Zawijasz wywołania do bazy danych protectedmetodami. Aby przetestować tę klasę, faktycznie testujesz fałszywy obiekt, który dziedziczy po prawdziwej klasie połączenia z bazą danych i zastępuje chronione metody.

W ten sposób rzeczywiste wywołania bazy danych nigdy nie są testowane jednostkowo, to prawda. Ale to tylko kilka wierszy kodu. I to jest do przyjęcia.

TobiMcNamobi
źródło
1
Jeśli zastanawiasz się, dlaczego odpowiadam na moje pytanie: Tak, to może być odpowiedź, ale nie jestem pewien, czy jest to właściwe.
TobiMcNamobi
-1

Testowanie z danymi zewnętrznymi jest testem integracyjnym. Test jednostkowy oznacza, że ​​testujesz tylko jednostkę. Najczęściej odbywa się to za pomocą logiki biznesowej. Aby umożliwić testowanie jednostki kodu, musisz przestrzegać kilku wskazówek, na przykład uniezależnić jednostkę od innych części kodu. Jeśli potrzebujesz danych jednostkowych podczas testu jednostkowego, musisz wymusić te dane z zastrzykiem zależności. Istnieją pewne ramy kpiny i karczowania.

DeveloperArnab
źródło