REST API Тестване с краставица

1. Общ преглед

Този урок дава въведение за Cucumber, често използван инструмент за тестване за приемане от потребителя, и как да го използвате в тестовете за REST API.

Освен това, за да направим статията самостоятелна и независима от всякакви външни REST услуги, ще използваме WireMock, библиотека за уеб услуги, която зашеметява и подиграва Ако искате да научите повече за тази библиотека, моля, вижте въведението към WireMock.

2. Корнишон - Езикът на краставицата

Краставицата е рамка за тестване, която поддържа поведенческо развитие (BDD), позволявайки на потребителите да дефинират операции на приложения в обикновен текст. Работи на базата на специфичния за домейна Gherkin език (DSL). Този прост, но мощен синтаксис на Gherkin позволява на разработчиците и тестерите да пишат сложни тестове, като същевременно го запазват разбираем дори за нетехнически потребители.

2.1. Въведение в корнишона

Корнишонът е ориентиран към реда език, използващ окончания на редове, отстъпи и ключови думи за дефиниране на документи. Всеки непразен ред обикновено започва с ключова дума Gherkin, последвана от произволен текст, който обикновено е описание на ключовата дума.

Цялата структура трябва да бъде записана във файл с разширение на функцията, за да бъде разпозната от Cucumber.

Ето един прост пример за документ от корнишони:

Feature: A short description of the desired functionality Scenario: A business situation Given a precondition And another precondition When an event happens And another event happens too Then a testable outcome is achieved And something else is also completed

В следващите раздели ще опишем няколко най-важни елемента в структурата на корнишони.

2.2. Особеност

Използваме файл Gherkin, за да опишем функция на приложението, която трябва да бъде тествана. Файлът съдържа ключовата дума Feature в самото начало, последвано от името на функцията на същия ред и незадължително описание, което може да обхваща няколко реда отдолу.

Парсерът за краставици пропуска целия текст, с изключение на ключовата дума Feature , и го включва само за целите на документацията.

2.3. Сценарии и стъпки

Структурата на корнишони може да се състои от един или повече сценарии, разпознати от ключовата дума Scenario . Сценарият е основно тест, позволяващ на потребителите да проверят възможностите на приложението. Той трябва да описва първоначален контекст, събития, които могат да се случат, и очаквани резултати, създадени от тези събития.

Тези неща се извършват с помощта на стъпки, идентифицирани от една от петте ключови думи: Дадено , Кога , Тогава , И и Но .

  • Дадено : Тази стъпка е да се постави системата в добре дефинирано състояние, преди потребителите да започнат да взаимодействат с приложението. А има предвид клауза може да се счита като предпоставка за случая на употреба.
  • Кога : A Когато стъпка се използва за описание на събитие, което се случва с приложението. Това може да бъде действие, предприето от потребители, или събитие, задействано от друга система.
  • След това : Тази стъпка е да се определи очакван резултат от теста. Резултатът трябва да бъде свързан с бизнес стойностите на тестваната функция.
  • И и Но : Тези ключови думи могат да се използват за заместване на горепосочените ключови думи, когато има няколко стъпки от един и същи тип.

Краставицата всъщност не различава тези ключови думи, но те все още са там, за да направят функцията по-четлива и в съответствие със структурата на BDD.

3. Реализация на краставица-JVM

Краставицата първоначално е написана на Ruby и е пренесена в Java с реализация на Cucumber-JVM, което е предмет на този раздел.

3.1. Зависимости на Maven

За да се използва Cucumber-JVM в проект на Maven, в POM трябва да се включи следната зависимост:

 io.cucumber cucumber-java 6.8.0 test 

За да улесним тестването на JUnit с краставица, трябва да имаме още една зависимост:

 io.cucumber cucumber-junit 6.8.0 

Като алтернатива можем да използваме друг артефакт, за да се възползваме от ламбда изразите в Java 8, които няма да бъдат разгледани в този урок.

3.2. Определения на стъпки

Сценариите за корнишони биха били безполезни, ако не се превърнат в действия и тук влизат в сила определенията за стъпки. По принцип дефиницията на стъпка е анотиран Java метод с прикачен шаблон, чиято задача е да преобразува стъпките на корнишън в обикновен текст в изпълним код. След анализирането на документ за характеристика, Cucumber ще търси дефиниции на стъпки, които съответстват на предварително дефинирани стъпки на корнишон за изпълнение.

За да стане по-ясно, нека разгледаме следната стъпка:

Given I have registered a course in Baeldung

И дефиниция на стъпка:

@Given("I have registered a course in Baeldung") public void verifyAccount() { // method implementation }

Когато Краставицата прочете дадената стъпка, тя ще търси дефиниции на стъпки, чиито анотиращи модели съвпадат с текста на корнишона.

4. Създаване и провеждане на тестове

4.1. Писане на файл с функции

Нека започнем с деклариране на сценарии и стъпки във файл с име, завършващо в разширението .feature :

Feature: Testing a REST API Users should be able to submit GET and POST requests to a web service, represented by WireMock Scenario: Data Upload to a web service When users upload data on a project Then the server should handle it and return a success status Scenario: Data retrieval from a web service When users want to get information on the 'Cucumber' project Then the requested data is returned

Сега записваме този файл в директория с име Feature , при условие, че директорията ще бъде заредена в пътя на класа по време на изпълнение, например src / main / resources .

4.2. Конфигуриране на JUnit за работа с краставица

За да може JUnit да е наясно с Cucumber и да чете файлове с функции при изпълнение, класът Cucumber трябва да бъде деклариран като Runner . Също така трябва да кажем на JUnit мястото за търсене на файлове с функции и дефиниции на стъпки.

@RunWith(Cucumber.class) @CucumberOptions(features = "classpath:Feature") public class CucumberIntegrationTest { }

Както можете да видите, елементът за функции на CucumberOption локализира файла с характеристиките, създаден преди. Друг важен елемент, наречен лепило , осигурява пътища до дефинициите на стъпки. Ако обаче дефинициите на тестовия случай и стъпка са в същия пакет като в този урок, този елемент може да отпадне.

4.3. Писане на дефиниции на стъпки

Когато Краставицата анализира стъпки, тя ще търси методи, коментирани с ключови думи за корнишън, за да намери съответстващите дефиниции на стъпки.

Изразът на дефиниция на стъпка може да бъде Редовен израз или Израз на краставица. В този урок ще използваме Краставични изрази.

Следва метод, който напълно съответства на стъпка от корнишони. Методът ще се използва за публикуване на данни в REST уеб услуга:

@When("users upload data on a project") public void usersUploadDataOnAProject() throws IOException { }

И тук има метод, съответстващ на стъпка от корнишони и взема аргумент от текста, който ще се използва за получаване на информация от REST уеб услуга:

@When("users want to get information on the {string} project") public void usersGetInformationOnAProject(String projectName) throws IOException { }

Както можете да видите, методът usersGetInformationOnAProject приема аргумент String , който е името на проекта. Този аргумент се декларира от {string} в анотацията и тук той съответства на Cucumber в текста на стъпката.

Като алтернатива бихме могли да използваме регулярен израз:

@When("^users want to get information on the '(.+)' project$") public void usersGetInformationOnAProject(String projectName) throws IOException { }

Note, the ‘^' and ‘$' which indicate the start and end of the regex accordingly. Whereas ‘(.+)' corresponds to the String parameter.

We'll provide the working code for both of the above methods in the next section.

4.4. Creating and Running Tests

First, we will begin with a JSON structure to illustrate the data uploaded to the server by a POST request, and downloaded to the client using a GET. This structure is saved in the jsonString field, and shown below:

{ "testing-framework": "cucumber", "supported-language": [ "Ruby", "Java", "Javascript", "PHP", "Python", "C++" ], "website": "cucumber.io" }

To demonstrate a REST API, we use a WireMock server:

WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());

In addition, we'll use Apache HttpClient API to represent the client used to connect to the server:

CloseableHttpClient httpClient = HttpClients.createDefault();

Now, let's move on to writing testing code within step definitions. We will do this for the usersUploadDataOnAProject method first.

The server should be running before the client connects to it:

wireMockServer.start();

Using the WireMock API to stub the REST service:

configureFor("localhost", wireMockServer.port()); stubFor(post(urlEqualTo("/create")) .withHeader("content-type", equalTo("application/json")) .withRequestBody(containing("testing-framework")) .willReturn(aResponse().withStatus(200)));

Now, send a POST request with the content taken from the jsonString field declared above to the server:

HttpPost request = new HttpPost("//localhost:" + wireMockServer.port() + "/create"); StringEntity entity = new StringEntity(jsonString); request.addHeader("content-type", "application/json"); request.setEntity(entity); HttpResponse response = httpClient.execute(request);

The following code asserts that the POST request has been successfully received and handled:

assertEquals(200, response.getStatusLine().getStatusCode()); verify(postRequestedFor(urlEqualTo("/create")) .withHeader("content-type", equalTo("application/json")));

The server should stop after being used:

wireMockServer.stop();

The second method we will implement herein is usersGetInformationOnAProject(String projectName ). Similar to the first test, we need to start the server and then stub the REST service:

wireMockServer.start(); configureFor("localhost", wireMockServer.port()); stubFor(get(urlEqualTo("/projects/cucumber")) .withHeader("accept", equalTo("application/json")) .willReturn(aResponse().withBody(jsonString)));

Submitting a GET request and receiving a response:

HttpGet request = new HttpGet("//localhost:" + wireMockServer.port() + "/projects/" + projectName.toLowerCase()); request.addHeader("accept", "application/json"); HttpResponse httpResponse = httpClient.execute(request);

We will convert the httpResponse variable to a String using a helper method:

String responseString = convertResponseToString(httpResponse);

Here is the implementation of that conversion helper method:

private String convertResponseToString(HttpResponse response) throws IOException { InputStream responseStream = response.getEntity().getContent(); Scanner scanner = new Scanner(responseStream, "UTF-8"); String responseString = scanner.useDelimiter("\\Z").next(); scanner.close(); return responseString; }

The following verifies the whole process:

assertThat(responseString, containsString("\"testing-framework\": \"cucumber\"")); assertThat(responseString, containsString("\"website\": \"cucumber.io\"")); verify(getRequestedFor(urlEqualTo("/projects/cucumber")) .withHeader("accept", equalTo("application/json")));

Finally, stop the server as described before.

5. Running Features in Parallel

Cucumber-JVM natively supports parallel test execution across multiple threads. We'll use JUnit together with Maven Failsafe plugin to execute the runners. Alternatively, we could use Maven Surefire.

JUnit runs the feature files in parallel rather than scenarios, which means all the scenarios in a feature file will be executed by the same thread.

Let's now add the plugin configuration:

 maven-failsafe-plugin ${maven-failsafe-plugin.version}   CucumberIntegrationTest.java  methods 2     integration-test verify    

Note that:

  • паралелно: могат да бъдат класове, методи или и двете - в нашия случай класовете ще накарат всеки тестов клас да работи в отделна нишка
  • threadCount: показва колко нишки трябва да бъдат разпределени за това изпълнение

Това е всичко, което трябва да направим, за да стартираме паралелно функциите на Краставицата.

6. Заключение

В този урок разгледахме основите на Cucumber и как тази рамка използва специфичния за домейна Gherkin език за тестване на REST API.

Както обикновено, всички примерни кодове, показани в този урок, са достъпни в GitHub.