Testy jednostkowe vs testy integracyjne

1

Na ile mozna robic wszystko testami jednostkowymi a na ile integracyjnymi?

Czesto robilem ile sie da jednostkowymi + np. Cale CRUDy przez wszystkie warstwy integracyjnymi.

Niby spoko. Ale aplikacja puchnie. Testow coraz wiecej a odpalanie ich zajmuje coraz wiecej czasu.

Jak to wywazyc? Widzialem sugeste mockowania db layer... Ale w ten sposob nie wykryje problemow z transakcjami albo z konkretna baza danych...

0

Jakie problemy z transakcjami albo bazą danych chcesz wykrywać w testach?

0

Np. W integracyjnych zobacze lazy initialization exception. W jednostkowych nie.

0

Chce przetestowac poprawne interakcje na db. Zapis i poprawny odczyt, ze zapisuje sie dokladnie iles tam rekordow a nie duble. Exceptiony itp.

Moj wniosek jest taki:

  • dla developmentu in memory db typu h2
  • przed produkcja jenkins stestuje np. Nightly buildami na wersji bazy jak na produkcji
0

Z testami integracyjnymi db tez potrafi byc taki problem, ze owywanie danych do testow potrafi byc slamazarne gdy chcemy przetestowac cos duzego.

0

Przede wszystkim, testy jednostkowe odpala się na bieżąco, integracyjne tylko jeśli jednostkowe przejdą. Jeśli chodzi o wstawianie danych testowych, to może warto rozważyć przywracanie wejściowego stanu bazy z jakiegoś backupu, albo przy użyciu bulk insertów.
Jak dużo czasu zajmują te testy, że stanowi to problem?

0

Jak developuje uzywajac tdd np. Nowego cruda to raczej na biezaco powstaja mi testy jednostkowe i integracyjne. To odpalam ten kawalek na biezaco.

Bywa tez tak, ze musze zmienic gdzies cos powiazanego. Wtedy chce puscic calosc calosc, zeby zobaczyc przy okazji czy jakies inne testy sie nie zaczely wywalac.

Mechanizmy do recreate bazy mam. Chodzi bardziej o to, ze bywa, ze mozolnie buduje jakies rulsy, ktore przygotowuja mi stan bazy do dalszych testow. Junity sa zawsze sa prostsze i bardziej odizolowane.

0

Pisząc o "na bieżąco" miałem na myśli po każdym pushu do repozytorium. :)

Tak się zastanawiam, bo nigdy w sumie nie spotkałem się z tym, aby każdego CRUDa testować integracyjnie. Nie mówię, że to źle, ale co w ten sposób tak naprawdę testujesz - swój kod czy ORMa?

0

Chcę w ten sposób testować możliwą całość. Czyli od requestu, po zapis i odczyt. Wtedy jest też dość bezpiecznie zmieniać cokolwiek. Bo szybki feedback jak się coś wywala. Tylko zaczyna się problem gdy tego przybywa, a jenkins zaczyna mielić wszystkie testy zanim wrzuci nam na instancje 'dev' ;)

Czasami naprawdę łatwo coś zepsuć przez przeoczenie. W javie np. ktoś źle pobawi się @Transactional, porobi coś dziwnego z hashcodes i equalsami i nagle mamy tonę nowych rekordów w bazie itp. Unit testy nie są w stanie tego zrobić. A używając Mockito to mam wrażenie, że niczego za bardzo nie sprawdzam, tylko raczej opisuję 'jak chcę by to działało' .
Różne bazy mają też specific syntax, więc np. w przypadku przenosin i mając jakieś queries popisane testy integracyjne wskażą mi miejsca do poprawy.

może też podrzucę linka
https://www.petrikainulainen.net/programming/testing/writing-tests-for-data-access-code-unit-tests-are-waste/

4

Unit-testów używamy tam, gdzie API komponentu jest proste, a implementacja skomplikowana, ale niewymagająca zależności. Oczywiście dążymy do tego, aby interfejs każdego komponentu było prosty, niemniej niestety nie zawsze jest to możliwe. Przykładem takiego dobrego komponentu do testowania unit-testami byłoby np. malloc(). Interfejs prosty jak konstrukcja cepa, za to implementacja może być bardzo rozbudowana i nawet składać się z wielu prywatnych pod-komponentów (które zresztą też mogą mieć swoje unit-testy).

Z drugiej strony w systemach często są komponenty, których główną rolą jest pośredniczenie między innymi komponentami. Wszelkie proxy, adaptery, fasady, klasy DTO mapowane do bazy danych, itp. Takie komponenty nie mają same w sobie zbyt dużo logiki, za to mają bardzo dużo interakcji z resztą systemu, sklejają różne kawałki w całość i w związku z tym mają wiele zależności od innych komponentów lub wiele innych komponentów od nich zależy. Przykładem czegoś takiego może być np. handler Netty implementujący jakiś protokół, który dekoduje / przyjmuje żądania, konwertuje je i przesyła do innych komponentów, obsługuje wyjątki itp. Ale sam z siebie w sumie mało robi - głównie posługuje się innymi komponentami (inaczej musiałby być jakimś God-class). Co nie oznacza, że nie ma wcale kodu. Ba, może mieć bardzo dużo kodu, tyle że ten kod wygląda tak "weź ten obiekt A stąd i wyślij tam do B, a jak wróci w postaci C, to użyj komponentu D do przekonwertowania go na obiekt Z i wyślij go do Y". I jak to przetestować? Większość błędów jakie pojawiają się w tego typu komponentach wynika z nieprawidłowego użycia komponentów, z którymi takie "proxy" współpracuje. Np. nieprawidłowej kolejności wywołań, źle zainicjowanych argumentów, plików konfiguracyjnych w złym miejscu, niezrozumienia przez programistę semantyki pewnych operacji (niekoniecznie z winy programisty - czasami dokumentacja jest do d***, albo komponent robi co innego niż dokumentacja) itp.

Pisanie mocków do czegoś takiego to po pierwsze masakra, po drugie nie wychwyci błędów. Jak mockiem wyłapiesz, że np. wysyłasz błędne zapytanie? Jeszcze rozumiem, że zapytanie generujesz w wyniku jakiegoś złożonego procesu, ale jeśli wklepałeś je z palca w kodzie? To co taki test z mockiem Ci da? Jak wyłapiesz, czy zewnętrzny system faktycznie obsłuży to zapytanie i zwróci oczekiwane wyniki? Albo jak mockiem wyłapiesz, że nieprawidłowo zainicjowałeś jakiś tam framework i że np. plik konfiguracyjny jest w złym miejscu? Do takich rzeczy IMHO nadają się tylko testy integracyjne / funkcjonalne.

Wniosek: w dużym systemie zwykle będziesz potrzebował obu rodzajów testów. Jeśli system ma charakter integracyjny, to zapewne będziesz mieć przewagę testów integracyjnych. Jeśli system ma charakter bardziej obliczeniowy, algorytmiczny (np. AI do czegoś), to wtedy pewnie będziesz mieć więcej testów jednostkowych.

Ps. Całkowicie zgadzam się z treścią podlinkowanego artykułu. Unit testy są bez sensu do testowania kodu gadającego z bazą danych.

// dopisane:
A i jeszcze jedno: klienta nie będzie obchodzić, czy Twoje oprogramowanie wywali się z powodu błędu w Twoim kodzie, błędu użycia jakiejś biblioteki, czy z powodu błędu w samym frameworku/bibliotece, którego użyłeś. Powiadacie, że popularne frameworki i biblioteki nie mają błędów? Np. ORMy? Hahahahahaha. Kiedy robiłem szkolenia z Hibernate, Hibernate był już jakieś 5-6 lat na rynku (i był must-have w niemal każdym projekcie Javowym obok Springa). Miał błędy, który kończyły się zwracaniem nieprawidłowych wyników, albo potrafił generować może i poprawne semantycznie zapytania, ale zabijające bazę np. złączeniem kartezjańskim. Powodzenia w wyłapywaniu takich rzeczy unit-testami.

Dlatego w systemie musisz mieć jakieś testy, który testują wszystkie warstwy, łącznie z bibliotekami i frameworkami. Wiem, że to kosztuje niemało, ale z tego akurat właśnie nie można zrezygnować bez ryzykowania jakością produktu. Z unit-testów za to można zrezygnować, bo integracyjne i tak powinny wszystko wyłapać. Jedyna wada posiadania tylko integracyjnych/funkcjonalnych testów jest taka, że pewne błędy unit-testy mogą wyłapać i zlokalizować znacznie wcześniej i szybciej, bo szybciej się wykonują (a zatem można je uruchamiać częściej) i mają mniejszy zasięg.

0

A nie możesz testów integracyjnych przenieść do jakiegoś CI, np. Jenkinsa i niech on je robi po commicie jak jednostkowe u ciebie przejdą?

1

@Krolik generalnie się zgadzam ale:

Miał błędy, który kończyły się zwracaniem nieprawidłowych wyników, albo potrafił generować może i poprawne semantycznie zapytania, ale zabijające bazę np. złączeniem kartezjańskim. Powodzenia w wyłapywaniu takich rzeczy unit-testami.

Takie błędy generalnie developera "nie obchodzą". Jasne że klientowi bez różnicy co się wywala bo "ma działać", niemniej błąd w jakims third-party = wystawienie ticketa i czekanie aż poprawią, albo zrobienie jakiegoś obejścia. Ale tak czy siak nikt przecież nawet nie próbuje takich błedów wyłapywać unit testami, bo z zasady unit testy mają szukać błędów w naszym kodzie a nie w cudzym kodzie.

Z unit-testów za to można zrezygnować, bo integracyjne i tak powinny wszystko wyłapać

Nie widziałem nigdy testów integracyjnych ze 100% pokryciem, bo zwyczajnie wiele rzeczy bardzo trudno zasymulować. Szczególnie jeśli chodzi o obsługę błędów, sytuacji wyjątkowych etc, w efekcie testy integracyjne prawie zawsze są robione w wersji "happy execution path".

0

CI jakiś Jenkins przy czymś takim to must have. Niby fajnie.
Ale zazwyczaj mamy tego jenkinsa, który buduje nam kilka środowisk. W tym developerskie.

I jak leci mi 600+ testów, w tym sporo integracyjnych. Wszystkie lecą po prawdziwej bazie danych...

... To jest to wolne i w przypadku środowiska developerskiego minus. Pisząc testy chcesz mieć możliwie szybki feedback. Ale być może trzeba się z tym pogodzić.

Może opcja gdyby Jenkins budowal srodowisko dev na bazie typu h2 byłby jakims rozwiązaniem, a dalej puszczać to przed produkcją np. na Oracle.

Testy integracyjne można przyśpieszyć np. mając native sqle tam gdzie się da, któe przygotowują nam stan bazy do testów.

2

Jasne że klientowi bez różnicy co się wywala bo "ma działać", niemniej błąd w jakims third-party = wystawienie ticketa i czekanie aż poprawią, albo zrobienie jakiegoś obejścia.

Zgoda, tylko żeby wystawić ticketa, to trzeba najpierw błąd wyłapać. Po to robimy testy, aby takich błędów nie wyłapywał dopiero klient, tylko my. Dlatego właśnie uważam, że same testy jednostkowe nie są wystarczające. One testują, czy mój kod działa. Działanie mojego kodu jest konieczne, ale niewystarczające do działania całego produktu. Stąd potrzeba innych testów: integracyjnych i funkcjonalnych.

Co do szybkości testów, to my robimy tak, że odpalamy najpierw tylko te testy, które mają dużą szansę się wywalić. Produkt jest podzielony na moduły i często wystarczy testować jeden moduł. Natomiast jeśli takie testy przejdą, to przed mergem puszcze się testy pełne na jenkinsie. Ostateczny merge przecież można zrobić dzień później i niczego to nie blokuje.

0

A 600+ testów to taki powiedzmy już dość duży "mikroserwis".
A co w przypadku gdy mam tego więcej...

0

To nie każ programiście odpalać 600+ testów po każdej zmianie byle CSSa. -
@Krolik

To już mówię gdy Jenkins odpala całość i wrzuca np. na środowisko typu dev lub qa.

0

Ale każdego CRUDa? Rozumiem jakiś proces, który robi coś nietypowego, ale przy CRUDach wystarczy przetestować ograniczony zbiór operacji i typów danych, a nie każdy nowy przypadek, który jest identyczny z już istniejącymi. -
@somekind

Nowy CRUD zazwyczaj łączy się gdzieś w jakimś serwisie z czymś innym gdzie wyciąga jakieś dodatkowo potrzebne dane. Wtedy nie jest to izolowany przypadek.

0
Krwawy Szczur napisał(a):

Nowy CRUD zazwyczaj łączy się gdzieś w jakimś serwisie z czymś innym gdzie wyciąga jakieś dodatkowo potrzebne dane. Wtedy nie jest to izolowany przypadek.

Łączenie się z serwisem to nie jest CRUD. CRUD to operacje na źródle danych.

0

No to nie znam najbardziej ogólnej nazwy. Nazewnictwo jest wtórne.

Powiedzmy, że wystawiam nowe restowe api: get, put, delete, post.
Większość z tego operuje na nowych encjach, gdzieś tam w serwisie dociąga jakieś inne encje i mapuje na jakieś nowe dto.

Zresztą pisałem wcześniej. Testy integracyjne od rest api po persistence layer.

1 użytkowników online, w tym zalogowanych: 0, gości: 1