Konrad Kowalski (rootsher)Principal Platform & Reliability Architect000101000111101011110100010001010110111101010011

Contract tests i API tests: publiczna umowa systemu

data
kategoria
Testing
także w
Backend
czytanie
2 min / 454 słów

Zmiana pola w odpowiedzi API często wygląda jak refactor.

Kod jest czystszy.

Testy jednostkowe przechodzą.

Backend zwraca nowy kształt danych.

Problem zaczyna się dopiero wtedy, kiedy frontend, worker albo starsza aplikacja mobilna nadal oczekuje starej umowy.

To jest miejsce dla contract tests i API tests.

Co to za rodzaj testu

API test sprawdza zachowanie publicznego interfejsu.

Może dotyczyć:

text
REST endpoint
GraphQL query
RPC method
webhook
event consumer
public SDK

Contract test sprawdza umowę między producentem i konsumentem.

Umowa mówi:

text
co wolno wysłać
co można dostać
które pola są wymagane
które pola są opcjonalne
jak wyglądają błędy
co jest kompatybilną zmianą

API test może być jednostronny: system wystawia endpoint i sprawdzamy jego odpowiedź.

Contract test jest relacyjny: istnieje producent i konsument, a test pilnuje, żeby ich oczekiwania się nie rozjechały.

Schema tests

Schema test jest blisko tej samej warstwy.

Sprawdza, czy dane pasują do schematu:

text
OpenAPI
JSON Schema
GraphQL schema
AsyncAPI
Avro
Protobuf

Schema test nie udowadnia, że zachowanie biznesowe jest poprawne.

Udowadnia, że kształt danych mieści się w ustalonej strukturze.

To ważne, bo wiele awarii integracyjnych nie zaczyna się od algorytmu.

Zaczyna się od pola, które zniknęło, zmieniło typ albo stało się wymagane.

Co się dzieje, kiedy ich nie ma

Bez tych testów publiczna umowa zaczyna zależeć od pamięci zespołu.

Ktoś zmienia customerId na accountId.

Ktoś usuwa status, który "już nie jest używany".

Ktoś zmienia błąd 409 na 400, bo tak wygodniej w kontrolerze.

Każda z tych zmian może być lokalnie sensowna.

Ale dla konsumenta to może być breaking change.

Jeżeli testy łapią to dopiero przez E2E, problem jest już zasłonięty całą ścieżką użytkownika.

Jeżeli łapie to produkcja, mamy awarię kompatybilności.

Consumer-driven contracts

W systemach z wieloma konsumentami dobry kontrakt często wychodzi od konsumenta.

Konsument mówi:

text
potrzebuję tych pól
dla takiego requestu oczekuję takiej odpowiedzi
te błędy obsługuję

Producent uruchamia te oczekiwania u siebie.

To nie zastępuje testów producenta.

To dodaje brakującą informację: które fragmenty API są naprawdę używane i w jaki sposób.

Bez tego backend może utrzymywać formalny schemat, który wygląda dobrze, ale nie chroni realnych klientów.

Granica kompatybilności

Najważniejsze pytanie w contract testach brzmi:

text
czy ta zmiana jest kompatybilna dla istniejących konsumentów?

Dodanie opcjonalnego pola zwykle jest kompatybilne.

Usunięcie pola zwykle nie jest.

Zmiana typu zwykle nie jest.

Dodanie nowego wymaganego pola w requestcie prawie zawsze jest breaking change.

Zmiana semantyki bez zmiany schematu jest najtrudniejsza, bo schema test jej nie zobaczy.

Wtedy potrzebny jest API test opisujący zachowanie, nie tylko kształt.

Narzędzie ze stacka

Do API tests w backendzie Node wybrałbym Supertest.

Pozwala sprawdzić prawdziwe endpointy, status codes, headers i body bez uruchamiania pełnego środowiska.

Do contract tests dołożyłbym walidację względem OpenAPI albo JSON Schema.

W praktyce test powinien mówić:

text
endpoint zwraca zachowanie, którego oczekuję
odpowiedź mieści się w publicznym kontrakcie

Jeżeli API ma wielu konsumentów, schemat to za mało.

Wtedy warto dodać consumer-driven contracts, ale nadal trzymać je blisko pipeline producenta.

Kiedy to za mało

Contract test i API test nadal nie mówią, że użytkownik przejdzie całą ścieżkę.

Mogą potwierdzić, że endpoint działa, a kontrakt jest zgodny.

Nie potwierdzą, że przeglądarka, routing, stan UI, płatność i powrót użytkownika z bramki płatności składają się w całość.

Do tego potrzebne są E2E tests.