Testy kontraktowe
W ramach podcastu “Porozmawiajmy o IT” miałem okazję porozmawiać z Jackiem Milewskim o testach kontraktowych.
Posłuchaj naszej rozmowy w wersji audio 🎧 👇
Cześć!
Mój dzisiejszy gość to trener, architekt i programista, który modelując oprogramowanie, ma świadomość, jak zmienny jest software oraz jak ładny i bezpieczny może być, gdy tylko pragmatycznie podejść do projektowania, kodowania i testowania. Zadaje pytania, aby wyłuskać to, co istotne, a odfiltrować szum. Z tego buduje backend, frontend, infrastrukturę i zespół. W ramach potęgi IT Minds uczy innych oraz dzieli się swoim doświadczeniem. Moim i Waszym gościem jest Jacek Milewski.
Cześć, Jacku, bardzo miło mi gościć Cię w podcaście.
Cześć, Krzysztof, witam.
Dzisiaj będziemy rozmawiać z Jackiem o testach i to testach niebylejakich, bo takich opartych o kontrakty. I mam wrażenie, że się okaże, iż to nie tylko programiści powinni być zainteresowani tego typu testami, ale również powinniśmy angażować inne zespoły, bo tym bardziej dodadzą nam tutaj wartości do tych testów.
Ale zanim do tego przejdziemy, to takie, wiesz Jacku, otwierające, standardowe u mnie pytanie. Czy słuchasz podcastów? Jeśli tak, to być może masz jakieś ciekawe audycje do polecenia?
Tak, słucham. To nie jest tak, że słucham 120 podcastów, jestem w stanie teraz wylistować wszystkie. Głównie, jeżeli mówimy o IT, to będzie Mariusz Gil i jego Better Software Design. To jest jeden z takich bardziej wartościowych dla mnie podcastów, jeżeli chodzi o cały świat IT, więc to bym pewnie polecił.
Druga rzecz to jest taki podcast psychologiczny. To jest bardziej kanał zupełnie już miękki. Strefa Psyche Uniwersytetu SWPS. I tam prowadząca jest bardzo charyzmatyczna, tematy są bardzo ciekawe, te rozmowy, które prowadzi, są bardzo ciekawe. Tak że polecam.
Fajnie, dzięki za te polecajki. Oczywiście z tego miejsca pozdrawiamy Mariusza. Dobrze, Jacku, to co? Warto byłoby pewnie na początku zdefiniować, czym właściwie te testy kontraktowe są i dlaczego powstały? W sensie, na jaki problem odpowiadają?
Tak, problem pojawił się wtedy, kiedy musimy integrować dwa komponenty. W momencie, kiedy doszły mikroserwisy, to te dwa komponenty są rozproszone, tzn. po sieci celowo, bo chcemy mieć autonomię rozwijania tych mikroserwisów, autonomiczne zespoły, które wspólnie jednak składają się na wspólny system. Więc autonomia zespołu kończy się tam, gdzie zaczyna się autonomia drugiego zespołu. Nie chcemy się blokować, ale chcemy wiedzieć o sobie nawzajem, czy my będziemy jako cały system w stanie się dogadać.
I ten kontrakt to jest właśnie to nasze API, po jakim będziemy rozmawiać. W mikroserwisach to najczęściej będzie HTTP, gRPC czy asynchroniczne wiadomości, więc kontraktem będzie ścieżka bądź nazwa topicu i będą parametry wejściowe, parametry żądania i parametry odpowiedzi. Ja chcę jakieś konkretne pole od Ciebie otrzymać, i to jest mój kontrakt, bo Ty mi zakontraktowałeś, że mi je dasz.
Problem, jaki tu się pojawia, to pewnie przez skórę gdzieś tam już można wyczuć, bo to jest tak, że u mnie mój autonomiczny mikroserwis działa, ale jakimś sposobem na środowisko to już nie bardzo, bo tamci się nie wgrali, albo oni w ogóle nie wiedzieli, że mają się wgrać, albo nie wiedzieli, że ja oczekuję. Więc brakuje nam narzędzi do takiej komunikacji. Emulujemy to jakimiś kartkami papieru, jakbym to nazwał, bo dokumentacją na Confluence, dokumentacją na Swaggerze, w Wordzie, w Excelu, w czymkolwiek, czyli kartką papieru, nie chcemy się integrować kartką papieru.
Więc nawet jeżeli ja u siebie napisałem testy, ale boję się deploymentu, bo muszę jeszcze przetestować na środowisku, to byłby taki znak, że być może brakuje Ci tego narzędzia. To jest ten problem, który rozwiązuje to narzędzie. Są dodatkowe benefity, które trochę te narzędzia przemycają pod spodem, a które są zaskakująco dobre. Tzn. ja chcę wiedzieć, jako ta strona serwerowa, czyli wystawiający API, czy ktoś jeszcze w ogóle z tego API korzysta. A czy ktoś korzysta z tego konkretnego jednego pola w tym JSON-ie 20-polowym, czy ja je mogę już usunąć. To dostajemy trochę za darmo.
Więc tak, to byłby ten problem i to nie jest nowy problem, bo jak prowadzę szkolenia i się pytam, czy testujecie kontraktowo, padają odpowiedzi: No tak obwąchiwaliśmy trochę te narzędzia, temat, trochę gadaliśmy, ale to grubsza rzecz, żeby to wdrożyć, a de facto testy kontraktowe są stare jak świat, dlatego że jeżeli ja mam w Javie, w .NET albo w dowolnym języku programowania metodę, która jest wywołana przez inną, inny fragment kodu, to spróbujcie w tej metodzie usunąć jeden z parametrów. Tylko tak bez ID, bez tego automatycznego refaktoringu. Zobaczcie, że właśnie złamaliście kontrakt i świeci się na czerwono.
I takie coś chciałbym osiągnąć na poziomie mikroserwisowym. O ile na poziomie kompilatora to jest do ogarnięcia, bo kompilator to robił za nas, o tyle jak się wynosimy na poziom mikroserwisów, to już nie mamy jak tego zrobić inaczej niż na środowisku. A testy na środowisku są drogie, bo tam wszystko się rusza.
Okej, czyli mamy ten problem dogadywania się, tu powiedziałaś mikroserwisów, ale możemy sobie jakieś tutaj przyjąć nazewnictwo jako systemy, w różnej konfiguracji może to działać. Mamy ten problem właśnie, żeby ta komunikacja była up-to-date, czy posługiwała się zrozumiałym językiem, zrozumiałym kontraktem dla obydwu stron. Jak się tak trochę poszuka, to mamy kilka narzędzi, kilka rozwiązań tego problemu. Kartka papieru jest z pewnością jednym z nich. Jak wiemy, wszystko przyjmie, niekoniecznie łatwo to aktualizować. Jest Spring Cloud Contract, jest PACT. Chciałbym Cię zapytać, czy to są jedyne narzędzia, jedyne możliwości, czy tylko w ten sposób możemy zapewnić kompatybilność kontraktu, a może znasz jakieś lepsze rozwiązania?
Musimy mieć pełną świadomość tego, co jest na rynku, jakie mamy możliwości do rozwiązania problemu łamiących się kontraktów. Narzędzi jest kilka i te narzędzia, które tutaj faktycznie pierwsze się nasuwają, to są narzędzia specyficzne do tego i nimi będziemy się zajmować dalej, ale chciałbym, żebyśmy mieli pełen kontekst.
Te narzędzia Spring Cloud Contract i PACT udają trochę kompilator na poziomie HTTP, tzn. build nam sfailuje w momencie, kiedy usunę parametr, który ktoś oczekuje. Tak jakbym to usunął w metodzie w Javie. Ale to nie jest jedyna metoda, żeby tę zgodność kontraktu zapewniać, monitorować czy zabezpieczać. Po pierwsze możemy nie zmieniać kontraktu, to zawsze będzie dobra metoda, nie zmieniamy nic. Z jednej strony to jest niedorzeczne, ale z drugiej strony, gdyby na to popatrzeć pod kątem stabilizowania interfejsu, tzn. jestem platformowym narzędziem generycznym, które ma na tyle ustabilizowany i dojrzały kontrakt, że po prostu nie potrzebuje go tak często zmieniać. A w momencie, kiedy go zmieniam, potrzebuję wprowadzić zmianę, która byłaby niekompatybilna, publikuję nową wersję, czyli zupełnie oddzielny endpoint. Więc to jest jedna ze strategii.
Inna to jest usunięcie integracji, czyli w ogóle się nie integrujmy. To nie znaczy, że mamy rezygnować z jakiejś funkcjonalności, ale może jeżeli komponenty są blisko siebie, to może one powinny być jednym komponentem. I wtedy kontrakt nam testuje kompilator, tak jak wcześniej wspomnieliśmy.
Mamy jeszcze możliwość sprawdzania schematu JSON, czy schematu XML, JSON Schema, XML Schema, czy na Kafce możemy też sprawdzać format wiadomości. To oznacza, że po prostu nie dasz rady wyprodukować jako provider czy jako consumer wiadomości, która jest niezgodna z tym schematem. I tu jest delikatna różnica, znaczy znacząca różnica, ponieważ przy JSON Schema muszę dopasować się do całego schematu. Natomiast w testach kontraktowych zakładam, że ja nie muszę używać wszystkich pól z tego schematu. To są przykładowe requesty, na które ja się umawiam. To nie znaczy, że musisz dopilnować każdego pola w tym schemacie. Jeżeli mnie jako konsumenta interesuje tylko jedno.
I mamy jeszcze możliwość wypuszczenia SDK. Czyli znowu przenosimy kontrakt na poziom kompilatora Javy. Bo jeżeli wypuszczę SDK i zmienię nagłówek metody w tym SDK, to się po prostu komuś przestanie kompilować. Więc build mu nie przejdzie, więc osiągamy podobny wynik. Problem jest taki, że musimy tym SDK zarządzać, ale korzyści są takie, że to inni się do nas dopasowują, a my część zmian w API HTTP jesteśmy w stanie przemycić w tym SDK, nawet nie zmieniając tego API jobowego, więc daje to trochę plusów, poza tym SDK generuje się, no umówmy się, automatycznie z niektórych specyfikacji, więc to może być mniej bolesne.
Jeszcze spotkałem się z takimi momentami, że zespoły, jako że mamy Dockera, jeżeli mamy Dockera, to można go użyć, no i można de facto postawić sobie środowisko na lokalnym laptopie. Gdzieś tam 20 Dockerów i 19 zależności i ja tam 20, który chce się z nimi wszystkimi przetestować. Problem z tym zaczyna się już przy pierwszym de facto dockerze, bo muszę go skonfigurować. On ma też jakieś zależności, też ma jakąś konfigurację, jakiś ustawiony stan, a więc bardzo, bardzo duży coupling jest i muszę mieć świadomość tego, jak tam te serwisy działają, żeby ustawić im odpowiedni stan. Jakby każde z narzędzi daje nam jakiś zakres jakimś kosztem.
Te narzędzia Spring Cloud Contract i PACT udają trochę kompilator na poziomie HTTP, tzn. build nam sfailuje w momencie, kiedy usunę parametr, który ktoś oczekuje. Tak jakbym to usunął w metodzie w Javie. Ale to nie jest jedyna metoda, żeby tę zgodność kontraktu zapewniać, monitorować czy zabezpieczać. Po pierwsze możemy nie zmieniać kontraktu, to zawsze będzie dobra metoda, nie zmieniamy nic. Z jednej strony to jest niedorzeczne, ale z drugiej strony, gdyby na to popatrzeć pod kątem stabilizowania interfejsu, tzn. jestem platformowym narzędziem generycznym, które ma na tyle ustabilizowany i dojrzały kontrakt, że po prostu nie potrzebuje go tak często zmieniać. A w momencie, kiedy go zmieniam, potrzebuję wprowadzić zmianę, która byłaby niekompatybilna, publikuję nową wersję, czyli zupełnie oddzielny endpoint. Więc to jest jedna ze strategii.
👉 Czytaj dalej: https://porozmawiajmyoit.pl/poit-250-testy-kontraktowe/