kontakt@tujestbug.pl

Spis treści

DRY (Don’t Repeat Yourself)

Jedna z głównych zasad programowania, która ma na celu zapobieganie powstawaniu wielu wystąpień tego samego kodu w projekcie. Niestety jest często łamana przy pisaniu testów automatycznych, a nawet kodu produkcyjnego w aplikacjach. Główną motywacją jej łamania jest 'oszczędność czasu’ i niewystarczające umiejętności autorów. Prowadzi to niestety do sytuacji, w której na skutek np. zmiany funkcjonalności nie wystarczy poprawić jednej linijki kodu w jednej klasie, a trzeba poprawiać wiele linijek w wielu miejscach kodu.

W dzisiejszym artykule dowiesz się w jaki sposób możemy wdrożyć tę zasadę w pisaniu testów automatycznych Rest Api.

Gdzie można wykorzystać zasadę DRY w testach Rest Assured?

Wszystko tak naprawdę zależy od kontekstu projektu, ale generalna zasada jest taka, że jeśli daną akcję wykonujesz więcej niż raz, to prawdopodobnie możesz ją zamknąć w obrębie jednej klasy/metody i reużywać zamiast za każdym razem kopiować ten sam kod. Przykładowe miejsca dla testów Rest Assured:

  •  Specyfikacja żądań i odpowiedzi
  • Autoryzacja do serwera
  • Komunikacja z endpointami (np. 1 klasa na 1 endpoint aplikacji)
  • Klasy typu wrapper, które będą kompleksowo realizować nasze kroki testowe, np. utwórz zasób X, dodaj do niego Y i zwróć identyfikator zasobu X, aby kontynuować na nim dalsze testy.

Specyfikacja

Specyfikacja określa niejako dane kontraktu pomiędzy klientem i serwerem. Pozwala na zdefiniowanie takich informacji jak sposób kodowania, akceptowane nagłówki, kod odpowiedzi z serwera. Dodatkowo w ramach specyfikacji w RestAssured możemy określić jakie dane będą logowane i po której stronie komunikacji. Naturalnie każde żądanie, które przesyłamy do serwera powinno mieć zdefiniowaną specyfikację. Możemy to specyfikować każdorazowo lub możemy skorzystać z zasady DRY i wyspecyfikować konkretny sposób komunikacji dla całych grup żądań i odpowiedzi, które będą przesyłane pomiędzy nami a serwerem Rest Api.

RequestSpecification

Jest to interfejs biblioteki RestAssured, który definiuje, m.in. bazowy url serwera, nagłówki żądania, sposób logowania komunikacji, kodowanie czy typ przesyłanych informacji. Zastosowanie tego interfejsu pozwala na zapisanie sposobu komunikacji z serwerem w jednym miejscu kodu i współdzielenie go z wieloma testami, które będą wysyłać zapytania.

Rozpatrzmy następujący test:

				
					@Test
public void postmanShouldEchoHeaders(){
    given()
            .config(config().encoderConfig(EncoderConfig.encoderConfig()
                    .appendDefaultContentCharsetToContentTypeIfUndefined(false)))
            .baseUri("https://postman-echo.com")
            .header("myHeader", HEADER_VALUE)
    given()
        .when()
            .log().all()
            .post("/post")
        .then()
            .statusCode(200)
            .body("headers.myheader", equalTo(HEADER_VALUE));
}


				
			

Gdybyśmy mieli 100 testów, które komunikują się z tym samym URI (postman-echo.com) i przesyłają ten sam nagłówek to tak naprawdę linie 3-7 byłyby powtórzone stukrotnie. W rezultacie, gdyby choć jedna wartość uległa zmianie, zmuszeni bylibyśmy do dokonania poprawki w aż 100 miejscach!

Spróbujmy zrobić to prawidłowo z wykorzystaniem interfejsu RequestSpecification. W tym celu definiujemy referencje do interfejsu RequestSpecification i zwyczajnie przypisujemy mu cały kod, który określa komunikację z serwerem:

				
					private RequestSpecification requestSpecification;

@BeforeMethod
public void setup(){
    requestSpecification = given()
            .config(config().encoderConfig(EncoderConfig.encoderConfig()
                    .appendDefaultContentCharsetToContentTypeIfUndefined(false)))
            .baseUri("https://postman-echo.com")
            .header("myHeader", HEADER_VALUE)
            .log().all();
}


				
			
Kiedy już mamy tak zdefiniowaną referencję pozostaje tylko użyć jej jako argumentu do metody given  w pozostałej części testu:
				
					@Test
public void postmanShouldEchoHeaders(){
    given(requestSpecification)
        .when()
            .post("/post")
        .then()
            .statusCode(200)
            .body("headers.myheader", equalTo(HEADER_VALUE));
}

				
			
A co, jeśli kolejny test będzie wymagał dodatkowych informacji, jak np. kolejny nagłówek?Nic prostszego – po prostu rozszerzamy given  o dodatkowe wywołania metody:
				
					@Test
public void postmanShouldEchoTwoHeaders(){
    given(requestSpecification)
            .header("additionalheader", SECOND_HEADER_VALUE)
            .when()
            .post("/post")
            .then()
            .statusCode(200)
            .body("headers.myheader", equalTo(HEADER_VALUE),
                    "headers.additionalheader", equalTo(SECOND_HEADER_VALUE));
}

				
			

ResponseSpecification

Jest to analogiczny do poprzedniego interfejs biblioteki RestAssured. Pomoże nam w zdefiniowaniu naszych oczekiwań względem odpowiedzi z serwera. Możemy zawrzeć tu takie informacje, jak np. oczekiwany rodzaj danych, nagłówki odpowiedzi, jakie informacje będziemy logować czy nawet oczekiwany kod z serwera.

ResponseSpecification możemy zdefiniować za jednym zamachem wraz z definicją RequestSpecification:

W rezultacie nasz test uprości się do następującej formy:

				
					@BeforeMethod
public void setup(){
    requestSpecification = given()
            .config(config().encoderConfig(EncoderConfig.encoderConfig()
                    .appendDefaultContentCharsetToContentTypeIfUndefined(false)))
            .baseUri("https://postman-echo.com")
            .header("myHeader", HEADER_VALUE)
            .log().all();

    responseSpecification = expect()
            .statusCode(200)
            .contentType(ContentType.JSON)
            .body("headers.myheader", equalTo(HEADER_VALUE))
            .log().all();
}

				
			

Wzorzec budowniczego (Builder)

Opis wzorca stanowi zdecydowanie materiał na osobny artykuł więc na potrzeby tego artykułu opiszę go tu dość skrótowo. Budowniczy jest wzorcem projektowym utworzonym z myślą o elastycznym tworzeniu złożonych obiektów poza oryginalną klasą, której obiekt staramy się utworzyć. Dzięki takiemu rozwiązaniu możemy skupić się jedynie na opisaniu danych, które będą nam niezbędne do realizacji naszego zadania. W bibliotece RestAssured istnieje możliwość posłużenia się właśnie tym wzorcem w celu utworzenia specyfikacji dla żądania i odpowiedzi.

RequestSpecBuilder

Klasa pozwalająca na tworzenie referencji typu RequestSpecification. Pozwala na sprecyzowanie takich informacji jak ciasteczka, nagłówki, filtry, parametry, baseURI, logi itd. Aby utworzyć referencję typu RequestSpecification konieczne jest sprecyzowanie parametrów poprzez metody typu set~, a na końcu utworzenie referencji przez wywołanie metody .build() :

				
					@BeforeMethod
public void setup(){
    requestSpecification = new RequestSpecBuilder()
            .setBaseUri("https://postman-echo.com")
            .setConfig(config().encoderConfig(EncoderConfig.encoderConfig()
                    .appendDefaultContentCharsetToContentTypeIfUndefined(false)))
            .addHeader("myHeader", HEADER_VALUE)
            .log(LogDetail.ALL)
            .build();

    responseSpecification = expect()
            .statusCode(200)
            .contentType(ContentType.JSON)
            .body("headers.myheader", equalTo(HEADER_VALUE))
            .log().all();
}

				
			

ResponseSpecBuilder

Analogiczna klasa, która pozwala na tworzenie referencji typu ResponseSpecification. Podobnie jak w poprzedniej klasie możemy zdefiniować interesujące nas parametry komunikacji jak oczekiwany kod odpowiedzi, nagłówki, ciastka, zawartość ciała metody (body) itd.Po zastosowaniu wzorca budowniczego setup naszego testu będzie wyglądał następująco :
				
					@BeforeMethod
public void setup(){
    requestSpecification = new RequestSpecBuilder()
            .setBaseUri("https://postman-echo.com")
            .setConfig(config().encoderConfig(EncoderConfig.encoderConfig()
                    .appendDefaultContentCharsetToContentTypeIfUndefined(false)))
            .addHeader("myHeader", HEADER_VALUE)
            .log(LogDetail.ALL)
            .build();

    responseSpecification = new ResponseSpecBuilder()
            .expectStatusCode(200)
            .expectContentType(ContentType.JSON)
            .expectBody("headers.myheader", equalTo(HEADER_VALUE))
            .log(LogDetail.ALL)
            .build();
}

				
			

Natomiast sama metoda testowa nie ulegnie żadnej zmianie względem wersji bez Buildera:

				
					@Test
public void postmanShouldEchoHeaders(){
    given(requestSpecification)
        .when()
            .post("/post")
        .then()
            .spec(responseSpecification);
}

				
			

Połączmy kropki …

Wiemy już, że stosowanie RequestSpecification pozwoli nam na uniknięcie duplikowania kodu i trzymanie się zasady DRY. Pewnie domyślasz się też, że odpytując różne endpointy będziemy potrzebowali zdefiniować różne sposoby komunikacji dostosowane do danej funkcji aplikacji. Co możemy zrobić, aby zmniejszyć wysiłek związany z utrzymaniem naszych testów długoterminowo?

Najprostszym rozwiązaniem będzie utworzenie dedykowanej klasy, która będzie zwracać nam referencję do Request/ResponseSpecification, która będzie najbardziej zgodna z testowanym w danej chwili przez nas endpointem.

				
					public class MySpecBuilder {
    private static final String HEADER_VALUE = "tu_jest_bug";

    public static RequestSpecification getPostmanEchoRequestSpec(){
        return getPostmanEchoRequestSpec(Map.of("myHeader", HEADER_VALUE));
    }

    public static RequestSpecification getPostmanEchoRequestSpec(Map<String, String> headers){
        return new RequestSpecBuilder()
                .setBaseUri("https://postman-echo.com")
                .setConfig(config().encoderConfig(EncoderConfig.encoderConfig()
                        .appendDefaultContentCharsetToContentTypeIfUndefined(false)))
                .addHeaders(headers)
                .log(LogDetail.ALL)
                .build();
    }

    public static RequestSpecification getReqresInRequestSpec(){
        return new RequestSpecBuilder()
                .setBaseUri("https://reqres.in/api")
                .log(LogDetail.ALL)
                .build();
    }

    public static ResponseSpecification getPostmanEchoResponseSpec(){
        return new ResponseSpecBuilder()
                .expectStatusCode(200)
                .expectContentType(ContentType.JSON)
                .expectBody("headers.myheader", equalTo(HEADER_VALUE))
                .log(LogDetail.ALL)
                .build();
    }
}
				
			

I gotowe, a cały kod znajdziesz tutaj

Jeśli podobał Ci się artykuł zostaw komentarz i jeśli tego jeszcze nie zrobiłeś to dopisz się do listy subskrybentów, aby nie ominęły Cię kolejne artykuły z serii o RestAssured.

A poprzednie artykuły znajdziesz tutaj -> RestApi

Piotr


0 komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *