Niedawne wdrożenie spowodowało awarię trzech mikroserwisów, mimo że każdy test jednostkowy, test integracyjny i sprawdzenie za pomocą mock-servera zakończyły się sukcesem. Zespół zamienił swoje mocki API na testowanie kontraktowe. W ciągu sześciu miesięcy liczba kontraktów wzrosła z trzech do 47, a miesięczny wskaźnik awarii integracji spadł z dwóch incydentów do zera.

Dlaczego mocki nie mogą Cię chronić

Serwer mockujący jedynie naśladuje kształt, jakiego oczekuje konsument; nigdy nie sprawdza, czy dostawca faktycznie go dostarcza. Jeśli dostawca zmieni nazwę pola – np. z name na display_name – mock nadal zwraca starą odpowiedź, testy konsumenta pozostają zielone, a system produkcyjny ulega awarii. Awaria produkcyjna we wtorek o 14:00 polegała właśnie na tym: mock „skłamał” na temat rzeczywistego kontraktu.

Testowanie kontraktowe wypełnia tę lukę

Testowanie kontraktowe wymusza na obu stronach API uzgodnienie wspólnej definicji, zanim jakikolwiek kod trafi na produkcję. Istnieją dwa powszechne podejścia:

  • Kontrakty sterowane przez konsumenta (consumer-driven contracts) – usługa konsumująca zapisuje oczekiwania; usługa dostarczająca je waliduje. To dobrze sprawdza się w przypadku wewnętrznych mikroserwisów, które ewoluują wspólnie.
  • Kontrakty sterowane przez dostawcę (provider-driven contracts) – dostawca publikuje specyfikację; konsumenci sprawdzają swój kod pod jej kątem. Jest to standardowy wzorzec dla publicznych API.

Pierwsze podejście zazwyczaj zapobiega przerwaniu integracji wewnątrz architektury mikroserwisowej.

Jak działa kontrakt sterowany przez konsumenta

  1. Konsument pisze test opisujący dokładnie to, czego potrzebuje od dostawcy.
  2. Uruchomienie testu generuje plik pact – dokument JSON rejestrujący te oczekiwania.
  3. Dostawca uruchamia swoją rzeczywistą usługę względem pliku pact w swoim potoku CI.
  4. Jeśli dostawca zmieni pole, weryfikacja zakończy się niepowodzeniem, a proces budowania zostanie zablokowany.

Ponieważ weryfikacja odbywa się na rzeczywistym kodzie dostawcy, każda zmiana naruszająca kontrakt zostaje wykryta wcześnie, a nie po wdrożeniu.

Gdzie testy kontraktowe znajdują się w piramidzie testów

  • Testy jednostkowe – szybkie, testują odizolowaną logikę.
  • Testy kontraktowe – średniej prędkości, potwierdzają zachowanie umów API.
  • Testy end-to-end – wolne, sprawdzają pełne przepływy biznesowe.

Traktuj testy kontraktowe jako pomost między szybką informacją zwrotną z testów jednostkowych a szerokim pokryciem testów end-to-end. Skup się na punktach integracji, które psują się najczęściej, i zacznij od dwóch lub trzech krytycznych punktów końcowych.

Historia wdrożenia w praktyce

Zespół, który zainspirował ten artykuł, zaczął od trzech kontraktów obejmujących ich najbardziej podatne na błędy wywołania. Sześć miesięcy później mieli 47 kontraktów obejmujących większość ruchu między usługami. W tym okresie liczba incydentów związanych z przerwaniem API spadła z dwóch miesięcznie do zera.

Kiedy testowanie kontraktowe może nie być warte zachodu

  • Jesteś samodzielnym programistą utrzymującym wszystkie usługi w jednym repozytorium.
  • API jest wyjątkowo stabilne i nie zmieniło się od lat.
  • Budujesz tymczasowy prototyp, który wkrótce zostanie odrzucony.

W takich scenariuszach narzut związany z utrzymywaniem kontraktów może przewyższyć korzyści.

Potencjalne wady i sposoby ich łagodzenia

  • Przechowuj wersjonowane kontrakty wraz z kodem, który opisują.
  • Automatyzuj weryfikację w każdym przebiegu CI, aby uniknąć nieaktualnych kontraktów.
  • Przeglądaj zmiany w kontraktach w pull requestach, aby wyłapać przypadkowe naruszenia.

Wnioski

Jeśli nadal polegasz na ręcznie tworzonych mockach, aby przekonać samego siebie, że Twoje usługi mogą ze sobą rozmawiać, stawiasz na fałszywą obietnicę. Testowanie kontraktowe zamienia tę obietnicę w weryfikowalną umowę, wykrywając zmiany naruszające kontrakt, zanim trafią na produkcję, i – jak pokazują liczby zespołu – może całkowicie wyeliminować awarie integracji.