API nie jest definiowane wyłącznie przez jego kontroler PHP ani opublikowaną specyfikację. Definiują je również oczekiwania, które inne systemy już uwzględniły: ścieżka, nazwa pola, kod HTTP, format błędu lub kolejność wymagana do przechodzenia między stronami. Testy kontraktowe dla API PHP zamieniają te oczekiwania w zautomatyzowane weryfikacje przed scaleniem kodu lub jego wdrożeniem.
Celem nie jest uniemożliwienie wszelkiej ewolucji. Chodzi o wykrycie, czy zmiana modyfikuje uzgodnienie obserwowalne przez konsumenta, i o świadome podjęcie decyzji: zachować kompatybilność, wprowadzić okres przejściowy lub wersjonować interfejs. Jest to szczególnie istotne w wewnętrznych API obsługiwanych przez wiele zespołów, integracjach B2B oraz przepływach asynchronicznych, w których błąd może pojawić się kilka godzin po publikacji.
Co rozwiązują testy kontraktowe i czego nie zastępują

Test kontraktowy sprawdza, czy dostawca i konsument są zgodni co do interakcji: dla prawidłowego żądania dostawca generuje odpowiedź o uzgodnionej strukturze, typach i regułach. W odwrotną stronę konsument może deklarować, jakich żądań potrzebuje, a dostawca weryfikuje, czy potrafi je obsłużyć.
Takie podejście wykrywa niezgodności, które testy jednostkowe zwykle pomijają. Test jednostkowy może potwierdzić, że serializator zwraca customer_id; nie dowodzi jednak, że konsument nadal rozumie to pole, jeśli wcześniej oczekiwał customerId. Lokalny test integracyjny może obejmować endpoint, lecz niekoniecznie odzwierciedla rzeczywiste założenia każdej integracji.
Nie zastępują one innych mechanizmów kontrolnych:
- Testów jednostkowych, dla reguł domenowych, walidacji i transformacji.
- Testów integracyjnych, dla bazy danych, kolejek, cache, uwierzytelniania lub podłączonych usług.
- Testów end-to-end, dla kompletnych krytycznych ścieżek w kontrolowanych środowiskach.
- Testów bezpieczeństwa i wydajności, dla autoryzacji, nadużyć, ujawniania danych, opóźnień i przepustowości.
- Obserwowalności na produkcji, aby wykrywać konsumentów, którzy nadal używają wycofywanych zachowań.
Kontrakt nie poświadcza też, że odpowiedź jest poprawna z perspektywy biznesowej; poświadcza, że zachowuje zadeklarowaną formę i semantykę. Dlatego powinien mu towarzyszyć zestaw przykładów wyrażających istotne reguły, a nie wyłącznie puste schematy.
Co stanowi część kontraktu API
Kontraktem jest każde zachowanie, które konsument może zaobserwować i od którego zależy. Ograniczenie go do JSON-a pomyślnej odpowiedzi pomija najczęstsze źródła awarii. Dla każdej operacji warto uzgodnić co najmniej następujące elementy.
- Żądanie: metoda, ścieżka, parametry zapytania, nagłówki, treść, pola wymagane, formaty i limity.
- Odpowiedź: kod HTTP, istotne nagłówki, struktura, typy, pola opcjonalne, pola dopuszczające wartości null oraz formaty dat, walut lub identyfikatorów.
- Błędy: kody statusu, treść błędu, stabilny kod funkcjonalny i warunki, które go wywołują. Tekst przeznaczony dla ludzi może się zmieniać; kod taki jak
validation_failedlepiej nadaje się do automatyzacji. - Paginacja i filtrowanie: znaczenie
limit, kursora lub strony, stabilność kolejności, reprezentacja następnego kursora i obsługa pustych zbiorów. - Uwierzytelnianie i autoryzacja: akceptowany mechanizm, wymagane nagłówki, zakresy lub uprawnienia oraz różnica między nieprawidłowymi poświadczeniami, brakiem poświadczeń i odmową dostępu.
- Zdarzenia i webhooki: nazwa zdarzenia, wersja lub schemat payloadu, podpis, ponowne próby, identyfikator zdarzenia, niegwarantowana kolejność oraz oczekiwania dotyczące idempotencji.
Wymagalność właściwości i dopuszczanie null to niezależne reguły. Pole może być wymagane i dopuszczać null, opcjonalne i nie dopuszczać go, gdy występuje, albo opcjonalne i dopuszczać go, jeśli jest obecne. Podobnie pominięta właściwość, właściwość obecna z wartością null oraz właściwość z pustym ciągiem znaków to różne stany. Jeśli konsument interpretuje je odmiennie, kontrakt musi to wyrażać i testować.
Niewielkie zmiany, które mogą zepsuć konsumentów
Modyfikacja może wydawać się nieszkodliwa z perspektywy dostawcy, a być niekompatybilna z wygenerowanym klientem, restrykcyjnym walidatorem lub logiką biznesową. Zmiana liczby całkowitej na ciąg znaków, na przykład 42 na "42", psuje porównania i schematy. Uczynienie pola opcjonalnym samo w sobie nie określa, czy akceptuje ono null: pierwsza reguła definiuje, czy właściwość musi być obecna, a druga — prawidłowe wartości, gdy zostanie uwzględniona.
Inne ryzykowne zmiany to zwracanie 200 tam, gdzie wcześniej zwracano 201, zastąpienie pustej listy przez null, zmiana precyzji liczby dziesiętnej, zmiana nazwy kodu błędu albo zmiana kolejności paginacji bez uprzedzenia. Dodanie pola jest zwykle kompatybilne dla tolerancyjnych odbiorców, ale nie wtedy, gdy konsument waliduje zamknięty schemat lub oblicza podpisy dla całej treści.
Kompatybilność zależy od rzeczywistego uzgodnienia, a nie od pojedynczej reguły. Warto klasyfikować każdą zmianę według znanych konsumentów, zadeklarowanej tolerancji i krytyczności przepływu. Jeśli informacje te nie są znane, należy traktować to jako ryzyko, a nie jako korzystne założenie.
Wybór między specyfikacją, kontraktami konsumentów lub obu
Specyfikacja interfejsu, na przykład opis OpenAPI, dobrze sprawdza się jako wspólne źródło dla ścieżek, operacji, parametrów, schematów i odpowiedzi. Można ją walidować w pipeline, aby wykrywać niekompatybilne zmiany względem wersji referencyjnej. Jest użyteczna, gdy istnieje wielu konsumentów lub gdy klienci i dokumentacja są generowane z tej samej definicji.
Jednak schemat nie zawsze oddaje to, co ma znaczenie dla każdego konsumenta: kombinacje filtrów, określony błąd w warunku biznesowym lub zależność od wartości przykładowej. Kontrakty sterowane przez konsumentów deklarują konkretne interakcje potrzebne każdemu konsumentowi. Dostawca weryfikuje je względem swojej implementacji.
Stosowanie obu poziomów jest zwykle rozsądne: specyfikacja zarządza ogólną powierzchnią, a kontrakty konsumentów obejmują przepływy o wysokiej wartości lub semantykę trudną do sprowadzenia do schematu. Za każdy artefakt musi istnieć jasna odpowiedzialność. Jeśli specyfikacja nie jest aktualizowana wraz ze zmianą kodu, przestaje być źródłem prawdy i staje się fikcyjną dokumentacją.
Reprezentatywne przykłady i przypadki brzegowe
Przykład kontraktu powinien zawierać realistyczne dane pod względem struktury, a nie dane produkcyjne. Dla zasobu zamówienia uwzględnij przypadek z elementami, pusty przypadek, jeśli jest prawidłowy, identyfikatory w uzgodnionym formacie oraz pełne daty ze strefą czasową, jeśli taka jest konwencja. Dodaj przypadki odmowy autoryzacji, nieudanej walidacji, nieistniejącego zasobu i końcowej paginacji.
Unikaj utrwalania nieistotnych szczegółów, które mogą zasadnie się zmieniać, takich jak losowy identyfikator, bieżąca godzina lub kolejność właściwości JSON. Stosuj precyzyjne asercje dla tego, co stabilne, oraz jawną tolerancję dla tego, co zmienne. Każdy przykład powinien odpowiadać na znaną potrzebę; ogromna kolekcja wymyślonych odpowiedzi zwiększa nakład utrzymania bez podnoszenia zaufania.
Stopniowe wdrażanie w istniejącym API PHP
Nie trzeba modelować całego API, aby uzyskać wartość. Zacznij od inwentaryzacji konsumentów: aplikacji wewnętrznych, klientów B2B, procesów batch, aplikacji mobilnych, automatyzacji i odbiorców webhooków. Zarejestruj właściciela, kanał kontaktu, używaną operację, krytyczność i możliwość aktualizacji.
Następnie nadaj priorytet endpointom, które tworzą lub modyfikują zasoby, uwierzytelniają użytkowników, zasilają procesy finansowe lub uruchamiają automatyzacje. Ustal punkt odniesienia dla ich bieżącego zachowania za pomocą sprawdzonej specyfikacji i testów względem odtwarzalnej instancji API. W PHP test musi wykonywać rzeczywistą warstwę HTTP aplikacji, a nie wywoływać bezpośrednio klasy usługi: kontrakt obejmuje routing, middleware, serializację i obsługę wyjątków.
POST /api/orders
Authorization: Bearer token
Content-Type: application/json
{"items":[{"sku":"ABC-1","quantity":2}]}
201 Created
{"id":"ord_123","status":"pending","items":[...]}Powyższy przykład jest użyteczny tylko wtedy, gdy towarzyszą mu reguły: które pola są wymagane, czy id zawsze jest ciągiem znaków, jakie błędy zwraca nieprawidłowy SKU oraz czy stan początkowy jest gwarantowany. To te reguły należy przekształcić w asercje.
Walidacja w ciągłej integracji i przed wdrożeniem
Pipeline powinien zakończyć się niepowodzeniem przed scaleniem, jeśli implementacja nie spełnia zatwierdzonych kontraktów. Praktyczny przepływ obejmuje uruchomienie testów jednostkowych, podniesienie kontrolowanych zależności, uruchomienie API PHP z konfiguracją testową oraz walidację specyfikacji, kontraktów dostawcy i kontraktów reprezentatywnych konsumentów. Testy powinny używać izolowanych i deterministycznych danych, aby awaria była odtwarzalna.
W zgłoszeniu zmiany porównaj ponadto proponowaną specyfikację z opublikowaną wersją, aby wskazać usunięcia ścieżek, zaostrzenie wymagań obecności, zmiany w dopuszczaniu null, zmiany typów i usunięte odpowiedzi. Diagnostyka powinna wskazywać operację, interakcję i naruszoną regułę; zwykły błąd schematu wymusza zbyt wiele dochodzenia.
Przed wdrożeniem uruchom ten sam zestaw testów na artefakcie, który ma zostać opublikowany, a nie na innej kompilacji. Po wdrożeniu monitoruj kody błędów, zgłaszane przez klientów błędy deserializacji, użycie wersji i ruch do przestarzałych ścieżek. Wstępna walidacja zmniejsza ryzyko; nie zastępuje potwierdzenia zachowania pod rzeczywistym ruchem.
Kompatybilność, deprecjacja i bezpieczne wycofanie

Gdy zmiana nie jest kompatybilna, preferuj jawny okres przejściowy. Możesz dodać nowe pole, zachowując poprzednie, wprowadzić nową operację lub wersję oraz zakomunikować datę wycofania popartą sygnałami użycia. Deprecjacja jest okresem operacyjnym z właścicielami, komunikacją i pomiarem; nie tylko notatką w dokumentacji.
Nie wycofuj zachowania tylko dlatego, że upłynął termin, jeśli nie potrafisz zidentyfikować oczekujących konsumentów lub jeśli przepływ jest krytyczny. Gdy jest to możliwe, udostępniaj kontrolowane ostrzeżenia i metryki, aby lokalizować starsze użycie bez zmieniania odpowiedzi. Stopniowe udostępnianie nowej wersji pozwala obserwować błędy i korygować kontrakty przed rozszerzeniem zmiany.
Najczęstsze błędy to testowanie wyłącznie pomyślnych odpowiedzi, modelowanie komunikatów dla ludzi zamiast kodów błędów, zakładanie, że wszyscy klienci ignorują nowe pola, oraz niewłączanie rzeczywistych lub reprezentatywnych konsumentów. Testy kontraktowe przynoszą wartość, gdy odzwierciedlają uzgodnienia utrzymywane przez obie strony i są uruchamiane jako zwykły warunek dostarczania.



