1. Patrzymy nie na nazwę, ale na dwie osie
W rozmowach o testowaniu łatwo się pogubić już na poziomie słów. W jednym zespole „integration test" to prawie każdy test ze Spring. W innym — tylko live-server przez prawdziwe HTTP. Dlatego bardziej przydatne jest opieranie się nie na nazwie pliku, ale na dwóch praktycznych osiach: co w teście realnie działa i co tym testem w ogóle można udowodnić.
Pierwsza oś — zakres realnego środowiska. Czy uruchamiasz jeden obiekt Java w pamięci? Czy podnosisz warstwę web? Czy podnosisz pełny kontekst Spring? Czy używasz prawdziwego serwera HTTP? Czy dodajesz prawdziwy PostgreSQL w kontenerze? Im więcej prawdziwego świata robisz częścią testu, tym wyższy koszt uruchomienia i tym więcej potencjalnych miejsc, gdzie test może się posypać nie z tego powodu, który teraz badasz.
Druga oś — strefa dowodu. Jedne testy najlepiej wyłapują lokalne reguły, inne — kontrakty granicy HTTP, trzecie — wiring między warstwami, czwarte — niuanse realnego środowiska. Jeśli trzymać te dwie osie w głowie, od razu staje się prościej: wybierasz nie „modny" test, ale adekwatną głębokość weryfikacji dla konkretnego ryzyka.
2. Testy unit: najtańsza ochrona lokalnego sensu
Poziom unit to miejsce, gdzie sprawdzasz kawałek zachowania bez Spring, bez bazy, bez sieci i bez przypadkowej infrastruktury wokół. Taki test jest prawie zawsze szybki, precyzyjny i tani w diagnostyce. Jeśli pada, przyczynę zazwyczaj można zrozumieć w sekundy, a nie chodzić analizować pół aplikacji.
W ContentHub naturalnymi celami dla weryfikacji unit są obiekty policy, małe reguły domenowe, generowanie slug, ograniczenia przejść między statusami, lokalne transformacje danych. Wszystko to, co można uczciwie uczynić obserwowalnym jako zwykłą logikę Java. Na przykład reguła „approve jest dozwolony tylko z IN_REVIEW" pięknie żyje na tym poziomie.
// Obiekt policy: hermetyzuje regułę domenową (lokalna logika bez Spring/DB/HTTP)
class PublicationPolicy {
// Sprawdzamy, czy można wykonać approve z bieżącego statusu artykułu
boolean canApprove(ArticleStatus status) {
// Jedyny dozwolony stan dla approve — IN_REVIEW
// Każdy inny status (np. DRAFT) powinien zwracać false
return status == ArticleStatus.IN_REVIEW;
}
}
Taki kod nie potrzebuje ApplicationContext, MVC ani prawdziwej bazy, żeby wykryć regresję. Jeśli jutro ktoś „dla przyspieszenia procesu" zezwoli na approve z DRAFT, test unit powinien to natychmiast złapać. I to mocny argument za poziomem unit: tu łapiesz awarię tam, gdzie się narodziła, a nie po długim łańcuchu pośrednich efektów.
Ale ważne jest być uczciwym do końca. Test unit nie dowodzi, że endpoint HTTP wywołuje tę regułę poprawnie. Nie dowodzi, że dane zostały zapisane w bazie. Nie dowodzi, że security nie wpuszcza zbędnego użytkownika. Chroni lokalny sens. I właśnie dlatego jest tak cenny: nie obiecuje za dużo.
3. Testy slice: prawdziwa infrastruktura jednej warstwy
Gdy tylko ryzyko wychodzi z poziomu „czysta reguła" na poziom HTTP, JSON lub persistence, podejście unit zaczyna albo niedostatecznie pokrywać problem, albo degenerować się w teatr mocków. Właśnie tutaj w ekosystemie Spring Boot szczególnie przydatna jest idea test slices: podnosisz nie całą aplikację, ale kontrolowany przekrój, w którym realna staje się infrastruktura konkretnej warstwy.
Testy slice mają ważną filozofię. To nie jest „niekompletna integracja" ani kompromis z biedy. To normalny sposób uczciwe i szybkiego sprawdzenia warstwy. Dla warstwy web ważne jest, żeby realnie działały: routing, binding parametrów, validation, serializacja i tłumaczenie błędów. Dla kontraktów JSON ważne jest realne zachowanie serializatora. Dla warstwy danych ważne są zapytania, mapowania, ograniczenia i w ogóle zachowanie tej warstwy. We wszystkich tych przypadkach podnoszenie pełnego backendu nie jest konieczne — i często jest wręcz szkodliwe.
Dlatego poziom slice w tym kursie będzie ogromną częścią codziennej praktyki. Uczy bardzo przydatnej dyscypliny: robić realnym dokładnie ten kawałek infrastruktury, gdzie żyje ryzyko, a reszty nie ciągnąć bez powodu. To jeden z najbardziej profesjonalnych nawyków w zwykłym projekcie Spring Boot.
4. Testy integracyjne: gdy trzeba sprawdzić styk, a nie tylko warstwę
Test integracyjny zaczyna się tam, gdzie awaria żyje nie wewnątrz jednej warstwy, ale na połączeniu kilku części. Kontroler wywołuje serwis, serwis pracuje z repozytorium, obok podpięte są filtry security, obsługa błędów, konfiguracja, serializacja — i chcesz zobaczyć, jak to wszystko zachowuje się razem. Tutaj uczciwy test slice już nie wystarczy, bo interesujące ryzyko narodziło się właśnie na styku.
W projektach Spring Boot słowo integration często kojarzy się z pełnym kontekstem aplikacji. To normalne skojarzenie, ale ważna jest tu nie nazwa adnotacji, ale myśl: uruchamiasz wystarczająco szeroki kawałek systemu, żeby złapać wiring, konfigurację, tłumaczenie wyjątków, łańcuch controller → service → repository i inne wspólne efekty.
Cena tego poziomu też jest uczciwa. Dłużej startuje, głośniej pada i gorzej lokalizuje przyczynę awarii. Dlatego testy integracyjne są cenne nie swoją szerokością jako taką, ale tym, że zamykają właśnie te ryzyka, które naprawdę żyją między częściami systemu. Gdy tylko zapomnisz o tym warunku, zaczyna się nadużywanie pełnego kontekstu.
5. Live-server: gdy ważna jest prawdziwa wymiana HTTP
Jest jeszcze szerszy krok: test, który idzie nie przez lokalny wywołanie warstwy web, ale przez realnie podniesiony serwer HTTP. Z punktu widzenia developera to bardzo ważna granica. Wewnątrzprocesowy test web i live-server sprawdzają podobne scenariusze, ale nie tę samą rzeczywistość. W drugim przypadku aplikacja zachowuje się bliżej prawdziwego serwera, a test komunikuje się z nią jak zewnętrzny klient.
Co to dodaje? Po pierwsze, pewność co do prawdziwego HTTP round-trip: serwer rzeczywiście wystartował na porcie, żądanie naprawdę poszło i wróciła odpowiedź. Po drugie, lepiej widoczne stają się pewne szczegóły zachowania transportowego, filtrów i konfiguracji serwera. Po trzecie, ten tryb bardzo dobrze pokazuje, że klient „z zewnątrz" rzeczywiście zobaczy ten system, który obiecujesz, a nie tylko poprawne wywołanie wewnątrz JVM.
Ale live-server nie może być trybem domyślnym dla całego zestawu testów. Jest drogi. Jeśli sprawdzasz lokalną regułę workflow, live-server to po prostu wolna dekoracja. Jego siła ujawnia się tylko tam, gdzie pytanie naprawdę brzmi: „a co zobaczy prawdziwy klient HTTP?"
6. Testy kontenerowe: zachowanie zależy od infrastruktury
Testy kontenerowe są potrzebne nie po to, żeby wszystko zrobić „jeszcze poważniejszym", ale po to, żeby przestać okłamywać siebie co do zachowania zewnętrznego komponentu. Dla ContentHub głównym przykładem będzie tutaj prawdziwy PostgreSQL. Na uproszczonym poziomie dane mogą wyglądać normalnie, a na prawdziwej bazie nagle ujawniają się dialekt, constraints, timestamps, sortowania i migracje, których nie można uczciwie zasymulować ani w głowie, ani w przypadkowym testowym zamienniku.
Dlatego test kontenerowy to nie „ostatni i najwłaściwszy poziom". To osobne narzędzie dla osobnej kategorii ryzyka: gdy ważne jest zachowanie konkretnej infrastruktury. W takim teście płacisz czasem startu i dodatkową złożonością środowiska, ale za to dostajesz dużo uczciwszy obraz tam, gdzie realne środowisko naprawdę zmienia wynik.
Właśnie dlatego w dojrzałej strategii ścieżka kontenerowa zwykle pojawia się później i punktowo. Nie jako zamiennik wszystkich pozostałych testów, ale jako wzmocnienie dla szczególnie wrażliwych scenariuszy. To ważna myśl dla całego kursu: drożej nie znaczy lepiej. Drożej znaczy „warto stosować tam, gdzie jest dodatkowa wartość" 💡
7. Testowanie regresji
Jest jeszcze jedno słowo, które często chce się postawić w jednym rzędzie z unit i integration — regression. Ale zestaw regresyjny lepiej rozumieć nie jako osobną technologię, ale jako specjalnie wyselekcjonowaną część całego test suite. Jego sens nie w tym, żeby być największym ani najcięższym. Jego sens — dawać maksimum pożytku za rozsądny czas uruchomienia.
W normalnym projekcie backendowym regression suite jest prawie zawsze mieszany. Trafiają do niego szybkie i wartościowe testy unit, kilka kluczowych weryfikacji slice, kilka drogich, ale bardzo ważnych scenariuszy integracyjnych. Czasem trafiają do niego też weryfikacje live-server, jeśli cena jest uzasadniona. A testy kontenerowe częściej zostają w późniejszym i węższym podzestawie, bo są istotnie droższe w codziennym uruchamianiu.
Warto to zrozumieć już teraz: dobry suite to nie kupa jednakowo szerokich testów. To kompozycja różnych poziomów, gdzie każdy płaci za siebie zrozumiałą korzyścią. I logika regresji właśnie pomaga utrzymać tę kompozycję w zdrowym stanie.
8. Porównanie poziomów: co realnie udowadniają
Warto raz zobaczyć wszystkie poziomy obok siebie nie jako zestaw modnych słów, ale jako tabelę siły i granic.
| Poziom | Co zazwyczaj realnie działa | Najlepiej wyłapuje | Czego sam w sobie nie dowodzi | Cena |
|---|---|---|---|---|
| Unit | jedna klasa, reguła lub mały serwis | lokalne reguły biznesowe, transformacje, niezmienniki | kontrakt HTTP, wiring, prawdziwą bazę, security-chain | niska |
| Slice | jedna warstwa i jej infrastruktura | kontrakty JSON, granicę web i zachowanie konkretnej warstwy danych | pełnego łańcucha aplikacji i sąsiadujących warstw | niska / średnia |
| Integration | kilka warstw lub pełny kontekst | wiring, wspólną pracę controller/service/repository, konfigurację | niuansów transportowych live-server i zachowania realnej zewnętrznej infrastruktury, jeśli jest zastąpiona | średnia / wysoka |
| Live-server | prawdziwy serwer HTTP i wywołanie klienta | end-to-end zachowanie web oczami klienta | specyficznych cech zewnętrznej bazy lub integracji, jeśli nie uczestniczą naprawdę | wysoka |
| Container | aplikacja plus prawdziwy zewnętrzny komponent | zachowanie PostgreSQL, migracji, ograniczeń infrastrukturalnych | wszystkiego pozostałego „automatycznie"; to nie jest uniwersalny zamiennik całego zestawu testów | wysoka+ |
Ta tabela jest przydatna tym, że usuwa zbędną magię. Na przykład test integracyjny nie jest „lepszy od testu unit w ogóle". Po prostu dowodzi czegoś innego. A test unit nie jest „słabszy od live-server z definicji". Po prostu odpowiada na inne inżynierskie pytanie.
9. Jeden scenariusz użytkownika nie równa się jednemu testowi 💥
I tu pojawia się główny punkt dnia. Weź ten sam flow „editor submit → admin approve → public read". Jeśli patrzeć na niego oczami użytkownika, to jedna historia. Jeśli patrzeć oczami inżyniera jakości, to zestaw różnych ryzyk i co za tym idzie — zestaw różnych dowodów.
Lokalna reguła przejścia statusu najlepiej wyłapywana jest testem unit. Prawidłowy 404 na public read najuczciwiej sprawdza się na granicy web. Filtr po PUBLISHED żyje w warstwie danych. Skvozne połączenie controller → service → repository ma sens na poziomie integracyjnym. Prawdziwa wymiana HTTP — na live-server. Specyfika PostgreSQL — na poziomie kontenerowym.
Gdy tylko to staje się widoczne, znika pokusa szukania jednego „uniwersalnego" typu testu. I właśnie w tym momencie cała typologia przestaje wydawać się biurokracją. Zaczyna działać jak mapa ceny i pewności. Czyli dokładnie tak, jak powinna działać w profesjonalnym backendzie 🚀
10. Typowe błędy przy rozmawianiu o poziomach testów 💉
Błąd nr 1: uznawanie, że szerszy test jest automatycznie lepszy.
Szeroki test rzeczywiście widzi więcej środowiska, ale płaci za to prędkością i jakością diagnostyki. Jest przydatny tylko tam, gdzie ta szerokość daje nowe informacje.
Błąd nr 2: nazywanie poziomu po bibliotece, a nie po realnie podniesionym świecie.
Nazwa pliku SomethingIntegrationTest niczego jeszcze nie dowodzi. Patrz na faktyczne środowisko: co było prawdziwe, a co nie.
Błąd nr 3: oczekiwanie od testów unit gwarancji kontraktu HTTP lub zachowania bazy.
To nie wada testów unit, ale błędne oczekiwanie. Są dobre właśnie dlatego, że wyłapują lokalne ryzyka szybko i precyzyjnie.
Błąd nr 4: próba robienia wszystkiego przez live-server lub pełny kontekst.
Taki suite szybko staje się drogi, głośny i nieporęczny w codziennym użyciu. Jeśli developerowi nie chce się często uruchamiać testów, strategia już przegrywa.
Błąd nr 5: postrzeganie regression suite jako „największego zestawu testów".
Zestaw regresyjny jest wartościowy nie rozmiarem, ale ROI. Jego zadanie — szybko i regularnie dawać mocny sygnał przy zmianach, a nie imponować długością logu.
GO TO FULL VERSION