Agile QA – Testujeme neexistujúci kód (Java)

265

Troška teórie na začiatok

Možno trochu zavádzajúci názov blogu by sa dal asi zrozumiteľnejšie preložiť ako počas trvania šprintu mockujeme REST-API. Každopádne sa v tomto článku detailnejšie pozrieme na dva Java frameworky, ktoré simulujú REST API (wiremock a hoverfly). A jeden, v dnešnej dobe veľmi populárny testovací a validačný framework, rest-assured.

Častokrát na začiatku šprintu, developeri ešte nemajú naprogramovaný servis, ktorý bude po jeho dokončení testovaný QA tímom, aby mohol byť dodaný pre front-endový tím, ktorý ho bude využívať. Keďže celé QA oddelenie je súčasťou agilného engineering tímu, zúčastňuje sa celého životného cyklu vývoja. Načo teda čakať na development oddelenie, keď si QA inžinieri môžu pomôcť nachystaním testov vopred.

Na testovanie použijeme JUnit framework a rest-assured, ktorý bude po dokončení servisov na oddelení developmentu možné nezmenený použiť aj na testy finálnych REST-servisov.

Na mockovanie REST servisov použijeme wiremock a hoverfly. Nechcem v tomto článku porovnávať oba frameworky. Oba majú niektoré veľmi užitočné funkcie a stále sa vyvíjajú. Takže je len na vás, aby ste zistili, ktorý z nich najlepšie zodpovedá vášmu projektu.

Na demonštračné účely si teda predstavme jeden šprint, počas ktorého sa má vyvinúť back-end, ako REST servisy, ktoré budú obsahovať:

  1. Autorizačný servis na autorizáciu používateľa menom a heslom, ktorý bude generovať autorizačný token – bearer, ktorým sa v hlavičke headri každého requestu budeme autorizovať.
  2. Servis, ktorý pridá nové auto do firemnej flotily áut.
  3. Servis, ktorý umožní vyhľadávanie áut.

Príprava tasku pred zaradeníim do sprintu

Štandardne, pred začatím šprintu na „taskingu“ vznikne všeobecná dohoda engineering tímu, čo sa bude počas šprintu vyvíjať. Obyčajne potom Java architekt na firemnej wiki stránke pripraví analýzu budúceho REST back-endu. Napríklad takto:

Autenticate

POST
 /api/sessions
params:
1.	User
2.	Password

JSON response (200) 
{
  "idToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 ",
  "accessToken": "H2NzYWGeSSdidYU6",
  "refreshToken": "12XirLmjcPDMW9ECnm2m26tEBgPmmLBjByxoQHP5hHnA0",
  "tokenType": "BEARER",
  "expires_in": 28800
}

Create a new car

POST
/carstub

JSON for creating car
{
   "client": "IBM",
   "make": "Nissan",
   "model": "Tiida",
   "year": 2004,
   "ccm": 1600,
   "fuel": "Benzin",
   "seats": 5,
   "weight": 1200
}
response (201)
body
"New fleet member has been stored"

Searching car by name

GET
/carstub?q=Aston

JSON response (200) 
{
	"client" : "IBM",
	"make" : "Aston Martin", 
	"model" : "DB9", 
	"year" : 2004, 
	"ccm" : 1200, 
	"fuel" : "Benzin", 
	"seats" : 3, 
	"weight" : 900
}

Developeri teda budú vyvýjať back-end, ktorý

  1. získa autorizačný token po prihlásení menom a heslom (POST, /api/sessions)
  2. založí nového člena firemnej flotily áut (POST, /carstub)
  3. umožní vyhľadávanie auta podľa názvu (GET, /carstub?q=XXX)

WireMock

Pred prvým použitím frameworkov zapíšeme dependencies do pom.xml (maven termín – linka, alebo poznámk apod čiarou). Všimneme si použite všetkých troch frameworkov:

  1. wiremock
  2. io.rest-assured
  3. io.specto – hoverfly
  4. junit
  5. tempus-fugit- paralell test runpom.xml

 

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.rhe.qa.blog</groupId>
  <artifactId>robime.it</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>REST Assured with mock Serialization</name>
  <dependencies>
		<dependency>
			<groupId>com.github.tomakehurst</groupId>
			<artifactId>wiremock</artifactId>
			<version>2.1.12</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
		</dependency>
		<dependency>
			<groupId>io.rest-assured</groupId>
			<artifactId>rest-assured</artifactId>
			<version>3.0.1</version>
		</dependency>
        <dependency>
            <groupId>com.google.code.tempus-fugit</groupId>
            <artifactId>tempus-fugit</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>io.specto</groupId>
            <artifactId>hoverfly-java</artifactId>
            <version>0.3.6</version>
        </dependency>
	</dependencies>
</project>

 

Vytvárame stub I. (Car.java)

Pomocou wiremock-u si teda vytvoríme vlastný, mockovaný REST servis, nad ktorým neskôr vyrobíme JUnit testy. Najskôr ale ukážka pomocnej triedy, ktorou definujeme členov našej firemnej auto-flotily.

public class Car {

    String client;
    String make;
    String model;
    int year;
    int ccm;
    String fuel;
    int seats;
    int weight;

    public Car() {
    }

    public Car(final String client, final String make, final String model, final int year, final int ccm, final String fuel, final int seats, final int weight) {
        this.client = client;
        this.make = make;
        this.model = model;
        this.year = year;
        this.ccm = ccm;
        this.fuel = fuel;
        this.seats = seats;
        this.weight = weight;
    }
    public String getClient() {
        return this.client;
    }
    public String getMake() {
        return this.make;
    }
    public String getModel() {
        return this.model;
    }
    public int getYear() {
        return this.year;
    }
    public int getCcm() {
        return ccm;
    }
    public void setClient(final String client) {
        this.client = client;
    }
    public void setCcm(final int ccm) {
        this.ccm = ccm;
    }
    public String getFuel() {
        return fuel;
    }
    public void setMake(final String make) {
        this.make = make;
    }
    public void setModel(final String model) {
        this.model = model;
    }
    public void setYear(final int year) {
        this.year = year;
    }
    public void setFuel(final String fuel) {
        this.fuel = fuel;
    }
    public int getSeats() {
        return seats;
    }
    public void setSeats(final int seats) {
        this.seats = seats;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(final int weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Car (fleet member for " + client + " ) is a " + this.make + " " + this.model + " " + this.year + " " + this.ccm + " " + this.fuel + " " + this.seats + " " + this.weight;
    }
}

Vytvárame stub II. (CarStub.java)

Trieda CarStub.java už vytvára mockované servisy na pridanie a vyhľadávanie.

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;

public class CarStub {

    public CarStub() {
    }

    public void createCarStub() {

        // post - OK
        stubFor(post(urlEqualTo("/carstub")).willReturn(aResponse().withStatus(201)
                .withBody("New fleet member has been stored")));

        // get - OK
        stubFor(get(urlEqualTo("/carstub?q=Aston")).willReturn(

        aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\"client\" : \"IBM\",\"make\" : \"Aston Martin\", \"model\" : \"DB9\", \"year\" : 2004, \"ccm\" : 1200, \"fuel\" : \"Benzin\", \"seats\" : 3, \"weight\" : 900}")));

        // get - OK
        stubFor(get(urlEqualTo("/carstub?q=Nissan")).willReturn(

        aResponse().withStatus(200)
                .withHeader("Content-Type", "application/json")
                .withBody("{\"client\" : \"IBM\",\"make\" : \"Nissan\", \"model\" : \"Tiida\", \"year\" : 2016, \"ccm\" : 1600, \"fuel\" : \"Benzin\", \"seats\" : 5, \"weight\" : 1200}")));

        // get - FAILED
        stubFor(get(urlEqualTo("/carstub?q=Not-Existing-Car")).willReturn(

        aResponse().withStatus(404)
                .withHeader("Content-Type", "application/json")
                .withBody("{}")));

    }
}

JUnit testy proti wiremock-stubu

Počas behu testov sa wiremock naštartuje ako JUnit @Rule.

@Rule

public WireMockRule wireMockRule = new WireMockRule(port);

Tu stoji za povšimnutie, že port na ktorom bude mock servis počúvať je neskôr generovaný náhodne, aby sme umožnili paralelný beh testov.

@RunWith(ConcurrentTestRunner.class)
public class CarTests {
    int randomPort = ThreadLocalRandom.current().nextInt(9800, 9900 + 1);
    final int port = randomPort;
    Car myCar = new Car("IBM", "Nissan", "Tiida", 2004, 1600, "Benzin", 5, 1200);

    CarStub myCarStub = new CarStub();

    @Rule
    public WireMockRule wireMockRule = new WireMockRule(port);

    @Test
    public void testCarSerialization() {
        final Header authHeader = new Header("Authorization", "bearer " + this.bearer);
        myCarStub.createCarStub();
        given().contentType("application/json")
                .header(authHeader)
                .body(myCar)
                .and()
                .log()
                .body()
                .when()
                .post("http://localhost:" + port + "/carstub")
                .then()
                .assertThat()
                .body(equalTo("New fleet member has been stored"))
                .assertThat()
                .statusCode(201);
    }
    @Test
    public void testCarDeserializationAston() {
        myCarStub.createCarStub();
        final Car myDeserializedCar = get("http://localhost:" + port + "/carstub?q=Aston").as(Car.class);
        System.out.println(myDeserializedCar.toString());
        Assert.assertEquals("Check the car make", myDeserializedCar.getMake(), "Aston Martin");
    }
    @Test
    public void testCarDeserializationNotExists() {
        myCarStub.createCarStub();
        final Car myDeserializedCar = get("http://localhost:" + port + "/carstub?q=Not-Existing-Car").as(Car.class);
        System.out.println(myDeserializedCar.toString());
        Assert.assertEquals("Check the car make", myDeserializedCar.getMake(), null);
        final int statusCode = get("http://localhost:" + port + "/carstub?q=Not-Existing-Car").getStatusCode();
        Assert.assertEquals("Check the Stauscode", statusCode, 404);

    }
    @Test
    public void testCarDeserializationNissan() {
        myCarStub.createCarStub();
        final Car myDeserializedCar = get("http://localhost:" + port + "/carstub?q=Nissan").as(Car.class);
        System.out.println(myDeserializedCar.toString());
        Assert.assertEquals("Check the car make", myDeserializedCar.getMake(), "Nissan");
    }
    @Test
    public void testCarNotFoundStausCode() {

        myCarStub.createCarStub();

        given().contentType("application/json")
                .when()
                .get("http://localhost:" + port + "/carstub?q=Not-Existing-Car")
                .then()
                .assertThat()
                .assertThat()
                .statusCode(404);
    }
}

Paralelný beh JUnit testov proti wiremock – Eclipse

Hoverfly framework

Tento framework je použitý hlavne z didaktických dôvodov. Vytvoril som v ňom servis na autorizáciu poslaním (post) username a password. Rovnako som mohol použiť aj wiremock, ale chcel som demonštrovať ako sa v ňom dajú použiť aj “fejkové” URL, dokonca cez SSL.

@RunWith(ConcurrentTestRunner.class)
public class HoverFlyTestDemo {
    String bearer;

    @ClassRule
    public static HoverflyRule hoverflyRule = HoverflyRule.inSimulationMode(dsl(
            service("http://www.my-super-web.com").
                    get("/test")
                    .willReturn(success("Success", "text/plain")),

            service("https://get-auth-token.com").
                    post("/api/sessions")
                    .willReturn(
                            success("{\"idToken\":\" eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\",\"accessToken\":\"l2nbbEq8ZkdylacK\",\"refreshToken\":\"csNL68AnBVNpbFmGlYJNFw6YhDeAklwGSDAylnCLPWDUI\",\"tokenType\":\"BEARER\",\"expires_in\":28800}",
                                    "application/json")),

            service("www.badrequest.com")
                    .get("/req")
                    .willReturn(badRequest())));

    @Test
    public void testMyFirstStub() {
        given().when()
                .get("http://www.my-super-web.com/test")
                .then()
                .assertThat()
                .statusCode(200)
                .and()
                .body(equalTo("Success"));
    }
    @Test
    public void testAuthorize() {
        this.bearer = login();
        System.out.println("BEARER " + bearer);
        Assert.assertNotNull(bearer);
    }
    private String login() {
        String bearer;
        bearer = given().config(RestAssured.config()
                .sslConfig(new SSLConfig().relaxedHTTPSValidation()))
                .param("username", "fakeuser")
                .param("password", "fakepass")
                .when()
                .post("https://get-auth-token.com/api/sessions")
                .then()
                .statusCode(200)
                .extract()
                .path("idToken");
        return bearer;
    }
    @Test
    public void testStubBadRequest() {
        given().
                when()
                .get("http://www.badrequest.com/req")
                .then()
                .assertThat()
                .statusCode(400);
    }

}

Hoverfly sa pred zbehnutím JUnit testov aktivuje pomocou rule – @ClassRule. Vlastný test je v metóde testAuthorize(), ktorá demonštruje získanie tokenu cez SSL na fiktívnej URL. Takto získaný bearer token potom môžu ostatné testy pridávať do headrov dalších requestov. Napríklad:

final Header authHeader = new Header("Authorization", "bearer " + this.bearer);
        given().when().header(authHeader)
                .get("http://www.my-super-web.com/test")
                .then()
                .assertThat()
                .statusCode(200)
                .and()
                .body(equalTo("Success"));

Paralelný beh JUnit testov proti hoverfly

Dobrý článok? Chceš dostávať ďalšie?

Už viac ako 4 200 z vás dostáva správy e-mailom. Nemusíš sa báť, nie každé ráno. Len občasne.

I agree to have my personal information transfered to MailChimp ( more information )

Tvoj email neposkytneme 3tím stranám. Posielame naňho len informácie z robime.it. Kedykoľvek sa môžete odhlásiť.