Sobes.tech
Middle+

Opowiedz o testowaniu kontraktowym mikroserwisu.

sobes.tech AI

Odpowiedź od AI

Testy kontraktowe koncentrują się na weryfikacji interakcji między mikroserwisami na poziomie ich API (kontrakt). Celem jest upewnienie się, że konsument (serwis korzystający z API) i dostawca (serwis udostępniający API) przestrzegają określonego "kontraktu" (schemat zapytań/odpowiedzi, format danych, oczekiwania).

Podstawowe zasady:

  • Konsument-kierowany: Testy opierają się na oczekiwaniach konsumenta. Konsument określa, jakie dane i formaty są mu potrzebne.
  • Testy dwukierunkowe: Sprawdzana jest zarówno zdolność konsumenta (czy potrafi pracować z aktualnym kontraktem), jak i dostawcy (czy spełnia oczekiwania konsumenta).
  • Szybkość: Testy są wykonywane szybko, często w ramach pipeline CI/CD, ponieważ nie wymagają wdrożenia wszystkich zależnych usług.

Proces zazwyczaj obejmuje:

  1. Konsument tworzy testy opisujące jego oczekiwania wobec API dostawcy. Testy te generują "kontrakt".
  2. Kontrakt jest publikowany (np. w repozytorium centralnym).
  3. Dostawca używa opublikowanego kontraktu do weryfikacji własnej implementacji API. Uruchamia testy (zazwyczaj po stronie dostawcy), które sprawdzają, czy jego API jest zgodne z kontraktem.
  4. W przypadku rozbieżności między oczekiwaniami konsumenta a implementacją dostawcy, test kończy się niepowodzeniem, wskazując na naruszenie kontraktu.

Narzędzia:

  • Pact (najpopularniejszy)
  • Spring Cloud Contract
  • Swagger/OpenAPI z narzędziami walidacyjnymi

Zalety:

  • Wczesne wykrywanie błędów integracji: Problemy z kompatybilnością wykrywane są jeszcze przed wdrożeniem.
  • Zmniejszenie potrzeby testów integracyjnych: Redukuje zależność od skomplikowanych i wolnych testów end-to-end.
  • Niezależne testy: Pozwala na testowanie serwisów niezależnie od siebie.
  • Jasne określenie oczekiwań: Wyraźnie definiuje kontrakt między serwisami.

Wady:

  • Nie zastępuje w pełni testów integracyjnych (sprawdza tylko interakcję na poziomie API).
  • Wymaga dyscypliny w wersjonowaniu kontraktów.
  • Może być trudniejsze dla serwisów z bardzo skomplikowanymi kontraktami.

Przykład użycia z Pact:

  1. Konsument (np. serwis Order Service) używa biblioteki Pact do tworzenia testów po stronie konsumenta.
    // Przykład testu konsumenta z Pact JVM
    @ExtendWith(PactConsumerTestExt.class)
    @PactTestFor(providerName = "ProductService", port = "8080")
    public class ProductServiceContractTest {
    
        @Pact(consumer = "OrderService")
        public RequestResponsePact createPact(PactDslWithRequest r) {
            return r.given("a product with id 1 exists")
                    .uponReceiving("a request for product by id")
                    .path("/products/1")
                    .method("GET")
                    .willRespondWith()
                    .status(200)
                    .headers(Map.of("Content-Type", "application/json"))
                    .body(new PactDslJsonBody()
                            .stringValue("id", "1")
                            .stringValue("name", "Laptop")
                            .numberType("price", 1200.00))
                    .toPact();
        }
    
        @Test
        void testGetProductById(MockServer mockServer) throws IOException {
            // Logika wywołania API dostawcy przez mockServer
            // Sprawdzanie, czy konsument poprawnie obsługuje mockowaną odpowiedź
            HttpResponse response = Request.Get(mockServer.getUrl() + "/products/1").execute().returnResponse();
            assertThat(response.getStatusLine().getStatusCode(), equalTo(200));
        }
    }
    
  2. Podczas wykonywania testów po stronie konsumenta generowany jest plik kontraktu (.json).
  3. Plik ten jest publikowany w Pact Broker lub innym centralnym repozytorium.
  4. Dostawca (Product Service) używa biblioteki Pact (np. Pact Provider Verifier) do weryfikacji własnej implementacji API na podstawie kontraktu pobranego z Pact Broker.
    // Przykład testu dostawcy z Pact Provider Verifier
    @Provider("ProductService")
    @PactFolder("relative/path/to/pacts") // lub użyć Pact Broker
    public class ProductServiceVerificationTest {
    
        @TestTemplate
        @ExtendWith(PactVerificationInvocationContextProvider.class)
        public void verifyPact(PactVerificationContext context) {
            context.verifyInteraction();
        }
    
        @State("a product with id 1 exists")
        public void productWithId1Exists() {
            // Logika przygotowania stanu danych w serwisie dostawcy do testu
        }
    
        // Dodanie URL dostawcy, na którym działa testowana usługa
        @BeforeEach
        void beforeEach(PactVerificationContext context) {
            context.setTarget(new HttpTestTarget("localhost", 8080));
        }
    }
    
  5. Weryfikator po stronie dostawcy wywołuje rzeczywiste API dostawcy zgodnie z specyfikacją kontraktu i porównuje otrzymaną odpowiedź z oczekiwaną w kontrakcie.

Testy kontraktowe są ważną praktyką zapewniającą niezawodność i łatwość utrzymania systemów opartych na mikroserwisach.