Gdy moduły aplikacji działają poprawnie osobno, a mimo to cały proces zamówienia kończy się błędem, problem zwykle leży na styku komponentów. Testy integracyjne pozwalają sprawdzić, czy aplikacja, baza danych, zewnętrzne API, kolejki i inne usługi naprawdę współpracują zgodnie z założeniami. Wyjaśniam, co dokładnie bada ten etap, jakie są jego odmiany, jak go zaplanować i jak uniknąć testów, które dają złudne poczucie bezpieczeństwa.
Testy integracyjne pokazują, czy moduły działają razem w realnym scenariuszu
- Cel: sprawdzenie komunikacji między modułami, usługami i zależnościami.
- Zakres: aplikacja może współpracować z bazą danych, API, kolejką lub systemem płatności.
- Najlepsza strategia: łączenie komponentów małymi partiami, zamiast testowania całego systemu naraz.
- Środowisko: realne zależności, kontenery i kontrolowane dane dają bardziej wiarygodny wynik niż przypadkowe mocki.
- Równowaga: testy integracyjne uzupełniają jednostkowe i end-to-end, ale ich nie zastępują.
Co naprawdę sprawdzają testy integracyjne
Test integracyjny łączy co najmniej dwa elementy systemu i sprawdza, czy wymiana danych między nimi przebiega poprawnie. Mogą to być na przykład warstwa aplikacji i baza danych, backend i zewnętrzne API albo usługa zamówień i moduł płatności. Po angielsku ten etap określa się jako integration testing.
Nie chodzi wyłącznie o sprawdzenie, czy metoda zwraca oczekiwaną wartość. Badam również format żądania, nazwy pól, kody odpowiedzi, transakcje, obsługę błędów, autoryzację i zachowanie systemu przy opóźnieniu zależności. To właśnie na tych granicach często pojawiają się problemy, których nie widać w izolowanych testach jednostkowych.
Przykład z procesu zakupowego
Wyobraźmy sobie sklep internetowy. Moduł koszyka może prawidłowo obliczać cenę, a moduł płatności może poprawnie obsługiwać transakcję. Dopiero test obejmujący koszyk, magazyn, płatności i bazę danych pokaże, czy po opłaceniu zamówienia stan produktu zostanie zmniejszony, a klient otrzyma właściwy status.
W praktyce test może wykonać żądanie utworzenia zamówienia, zapisać dane w bazie, wysłać komunikat do kolejki i zasymulować odpowiedź operatora płatności. Jeśli jeden komponent oczekuje pola customerId, a drugi wysyła client_id, aplikacja może przejść testy jednostkowe, ale zawiedzie na granicy integracji.
Dlaczego ten etap jest potrzebny
Największą wartością testów integracyjnych jest wykrywanie błędów kontraktu. Kontrakt to uzgodniony sposób komunikacji między elementami systemu, obejmujący między innymi strukturę danych, typy wartości i możliwe odpowiedzi.
Ten rodzaj testów pomaga też potwierdzić, że konfiguracja środowiska jest poprawna. Nieprawidłowe uprawnienia do bazy, inna wersja schematu, błędny adres usługi albo nieobsługiwany format daty potrafią zatrzymać produkcyjny proces mimo wysokiego pokrycia kodu testami jednostkowymi.
Główne strategie łączenia modułów
Sposób łączenia komponentów wpływa na szybkość wykrywania błędów i łatwość znalezienia ich przyczyny. Nie ma jednej metody dobrej dla każdego projektu. Inaczej podejdę do małej aplikacji monolitycznej, a inaczej do systemu z kilkunastoma mikroserwisami.
| Strategia | Na czym polega | Kiedy ma sens | Ograniczenie |
|---|---|---|---|
| Big bang | Wszystkie moduły są łączone i testowane jednocześnie. | Mały system lub szybka kontrola prototypu. | Trudno ustalić, który komponent powoduje awarię. |
| Top-down | Test zaczyna się od głównych modułów, a niższe zależności są zastępowane zaślepkami. | Gdy najważniejszy jest przepływ biznesowy i interfejs aplikacji. | Zaślepki mogą ukryć problemy niższych warstw. |
| Bottom-up | Najpierw łączy się komponenty infrastrukturalne, na przykład repozytorium z bazą. | Systemy, w których kluczowa jest poprawność dostępu do danych. | Później trzeba osobno potwierdzić cały przepływ użytkownika. |
| Inkrementalna | Komponenty są dołączane małymi, kontrolowanymi krokami. | Większość aplikacji rozwijanych ciągle i w zespole. | Wymaga dobrego planu zależności oraz stabilnych danych testowych. |
Najczęściej wybieram podejście inkrementalne. Najpierw sprawdzam połączenie aplikacji z bazą, później dokładam komunikację z usługą zewnętrzną, a na końcu pełny scenariusz biznesowy. Dzięki temu wynik testu jest bardziej czytelny, a naprawa awarii nie zamienia się w przeszukiwanie całego systemu.
Mocki i stuby nadal bywają przydatne, ale powinny zastępować tylko te zależności, których nie da się bezpiecznie lub rozsądnie uruchomić. Nadmiar atrap prowadzi do sytuacji, w której test potwierdza zgodność z fikcyjnym zachowaniem, a nie z prawdziwą bazą, API czy brokerem wiadomości.
Jak zaplanować testy integracyjne krok po kroku
Dobre testy zaczynają się od określenia ryzyka, a nie od wyboru biblioteki. Zanim napiszę pierwszy scenariusz, sprawdzam, które połączenia mają największe znaczenie dla użytkownika i gdzie pojedynczy błąd może przerwać cały proces.
- Wybierz krytyczny przepływ. Zacznij od logowania, utworzenia zamówienia, wystawienia faktury albo zapisu danych, czyli procesu, którego awaria ma realne konsekwencje.
- Rozpisz zależności. Zaznacz aplikację, bazę, API, kolejki, system autoryzacji i inne usługi biorące udział w scenariuszu.
- Ustal granice testu. Zdecyduj, które elementy uruchomisz naprawdę, a które zastąpisz kontrolowanym stubem. Granica powinna wynikać z ryzyka, nie z wygody.
- Przygotuj dane początkowe. Każdy test powinien znać stan bazy i nie zależeć od kolejności uruchamiania innych testów.
- Sprawdź wynik z kilku stron. Nie wystarczy kod HTTP 200. Zweryfikuj odpowiedź, zapis w bazie, wysłany komunikat i zmianę stanu procesu.
- Dodaj scenariusze błędów. Przetestuj timeout, brak rekordu, odrzuconą płatność, niepoprawne dane i niedostępność zależności.
Dobry scenariusz jest możliwie krótki, ale sprawdza pełny kontrakt. Dla endpointu tworzącego zamówienie może to oznaczać wysłanie żądania, odczyt odpowiedzi, sprawdzenie rekordu w bazie oraz potwierdzenie, że komunikat trafił do właściwej kolejki.
Kluczowa jest również powtarzalność. Jeżeli test raz przechodzi, a raz kończy się błędem bez zmiany kodu, zespół zaczyna ignorować sygnały. Przyczyną może być współdzielona baza, zależność od czasu, losowe dane, niekontrolowana kolejność testów albo usługa zewnętrzna dostępna tylko czasami.
Testy jednostkowe, integracyjne i end-to-end
Te trzy poziomy nie konkurują ze sobą. Każdy odpowiada na inne pytanie, dlatego rozsądna strategia testów potrzebuje ich właściwego połączenia.
| Rodzaj testu | Co sprawdza | Szybkość | Typowy problem |
|---|---|---|---|
| Jednostkowy | Jedną funkcję, klasę lub mały fragment logiki w izolacji. | Najczęściej bardzo wysoka. | Może nie wykryć błędu komunikacji z prawdziwą zależnością. |
| Integracyjny | Współpracę kilku modułów albo aplikacji z konkretną usługą. | Średnia, zależna od liczby uruchamianych komponentów. | Może być trudniejszy w konfiguracji i diagnozie. |
| End-to-end | Cały proces z perspektywy użytkownika, często przez interfejs. | Najczęściej najniższa. | Jest kosztowny w utrzymaniu i wrażliwy na zmiany UI oraz środowiska. |
Test jednostkowy odpowiada na pytanie, czy fragment kodu działa poprawnie. Test integracyjny pyta, czy moduły rozumieją się wzajemnie, a test end-to-end sprawdza, czy użytkownik może przejść przez cały scenariusz. Mylenie tych poziomów prowadzi albo do zbyt wielu wolnych testów, albo do luk w kontroli zależności.
Nie trzymałbym się sztywno proporcji typu 70-20-10. To przydatna ilustracja, ale nie uniwersalne prawo. Aplikacja oparta na wielu usługach, kolejkach i bazach może potrzebować większej liczby testów integracyjnych niż prosty kalkulator działający lokalnie.

Narzędzia i praktyki, które zwiększają wiarygodność
Współczesne zespoły często uruchamiają zależności w kontenerach. Baza danych, broker wiadomości czy serwis pomocniczy startują na czas testu, mają znaną konfigurację i są usuwane po zakończeniu. Takie podejście ogranicza różnice między komputerem programisty a środowiskiem CI.
Przydatne są także wbudowane serwery testowe frameworków, biblioteki do tworzenia tymczasowych baz oraz narzędzia do testowania kontraktów API. Wybór konkretnej technologii zależy od języka i architektury, ale zasada pozostaje ta sama: testuj realny sposób komunikacji, gdy właśnie on jest źródłem ryzyka.
Mock, stub czy prawdziwa usługa
Mock dobrze sprawdza się wtedy, gdy chcesz kontrolować odpowiedź zależności albo wymusić rzadki scenariusz błędowy. Nie potwierdzi jednak, czy prawdziwe API przyjmuje właściwy nagłówek, czy baza akceptuje zapytanie i czy schemat danych jest zgodny z aplikacją.
Moje praktyczne kryterium jest proste. Jeżeli zależność jest stabilna, dostępna lokalnie i ma istotny wpływ na wynik, uruchamiam ją naprawdę. Jeżeli jest droga, zewnętrzna, limitowana albo trudna do kontrolowania, używam stuba, a zgodność interfejsu zabezpieczam osobnym testem kontraktowym.
Przeczytaj również: Czarna skrzynka - Prawdziwe nazwy i testy rejestratorów lotu
Uruchamianie w potoku CI
Testy integracyjne powinny działać automatycznie przy zmianach dotyczących kodu, schematu bazy, konfiguracji i kontraktów API. Nie każdy scenariusz musi uruchamiać się przy każdym commicie, ale krytyczne przypadki powinny dawać szybki sygnał przed scaleniem zmian.
Dobrym rozwiązaniem jest podział na krótszy zestaw kontrolny oraz pełniejszy zestaw uruchamiany przed wydaniem. Każdy test powinien mieć czytelny komunikat błędu, logi zależności i możliwość odtworzenia danych, inaczej samo wykrycie awarii nie przełoży się na szybką naprawę.
Błędy, które najbardziej obniżają wartość testów
Najczęstszy błąd polega na sprawdzaniu wyłącznie pozytywnej ścieżki. System działa, gdy wszystko jest dostępne, ale nie wiadomo, co stanie się po odrzuceniu płatności, zerwaniu połączenia z bazą albo otrzymaniu niepełnej odpowiedzi z API.
- Współdzielone dane sprawiają, że jeden test zmienia warunki dla kolejnego.
- Zbyt szeroki zakres utrudnia znalezienie źródła problemu i wydłuża wykonanie.
- Nadmiar mocków ukrywa błędy zgodności z prawdziwą usługą.
- Brak czyszczenia środowiska powoduje zależność od kolejności testów.
- Losowe oczekiwanie na odpowiedź tworzy testy niestabilne i trudne do zaufania.
- Sprawdzanie tylko kodu odpowiedzi może przepuścić błędny zapis albo niepoprawny komunikat.
Przestrzegam też przed testami, które odtwarzają całą produkcję lokalnie. Brzmi to ambitnie, ale często kończy się ciężką konfiguracją, długim startem i różnicami między komputerami. Lepiej mieć mały, kontrolowany wycinek środowiska, który wiarygodnie odwzorowuje najważniejsze zależności.
Nie każdy problem wymaga testu integracyjnego. Czystą regułę biznesową lepiej pokryć testem jednostkowym, a pełny scenariusz użytkownika zostawić dla ograniczonej liczby testów end-to-end. Integracja ma największą wartość tam, gdzie ryzyko wynika z połączenia komponentów, a nie z samej logiki.
Jak dobrać zakres do konkretnego projektu
W małej aplikacji zacząłbym od kilku przepływów obejmujących bazę danych i najważniejsze endpointy. W systemie mikroserwisowym priorytetem będą kontrakty między usługami, obsługa komunikatów, idempotencja oraz zachowanie przy opóźnieniach i ponowieniach.
Najlepszy plan nie obejmuje wszystkiego po trochu. Powinien zabezpieczać te miejsca, w których awaria jednej granicy zatrzymuje proces biznesowy. Jeżeli test integracyjny potwierdza realną komunikację, działa powtarzalnie i pozwala szybko wskazać przyczynę błędu, spełnia swoje zadanie znacznie lepiej niż setki przypadkowych scenariuszy.
Traktuję ten etap jako most między kodem a działającym produktem. Testy jednostkowe pokazują, że elementy są poprawne osobno, lecz dopiero testy integracyjne pozwalają sprawdzić, czy z tych elementów powstaje spójny i odporny system.