Тестирование API REST с огурцом
1. обзор
Это руководство знакомит сCucumber, широко используемым инструментом для приемочного тестирования пользователей, и с тем, как использовать его в тестах REST API.
Кроме того, чтобы сделать статью автономной и независимой от любых внешних REST-сервисов, мы будем использовать WireMock, библиотеку веб-сервисов-заглушек и насмешек. Если вы хотите узнать больше об этой библиотеке, обратитесь кintroduction to WireMock.
2. Корнишон - язык огурца
Cucumber - это инфраструктура тестирования, которая поддерживает Behavior Driven Development (BDD), позволяя пользователям определять операции приложения в виде простого текста. Работает на основеGherkin Domain Specific Language (DSL). Этот простой, но мощный синтаксис Gherkin позволяет разработчикам и тестировщикам писать сложные тесты, оставляя их понятными даже для нетехнических пользователей.
2.1. Знакомство с корнишоном
Gherkin - это линейно-ориентированный язык, использующий окончания строк, отступы и ключевые слова для определения документов. Каждая непустая строка обычно начинается с ключевого слова Gherkin, за которым следует произвольный текст, который обычно является описанием ключевого слова.
Вся структура должна быть записана в файл с расширениемfeature, чтобы Cucumber распознал ее.
Вот простой пример документа Gherkin:
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, пропускается парсером Cucumber и включается только в целях документации.
2.3. Сценарии и шаги
Структура Gherkin может состоять из одного или нескольких сценариев, распознаваемых ключевым словомScenario. Сценарий - это в основном тест, позволяющий пользователям проверять возможности приложения. Он должен описывать начальный контекст, события, которые могут произойти, и ожидаемые результаты, созданные этими событиями.
Эти действия выполняются с помощью шагов, обозначенных одним из пяти ключевых слов:Given,When,Then,And иBut.
-
Given: этот шаг предназначен для перевода системы в четко определенное состояние перед тем, как пользователи начнут взаимодействовать с приложением. ПредложениеGiven можно рассматривать как предварительное условие для варианта использования.
-
When: ШагWhen используется для описания события, которое происходит с приложением. Это может быть действие, предпринятое пользователями, или событие, вызванное другой системой.
-
Then: На этом шаге указывается ожидаемый результат теста. Результат должен быть связан с бизнес-ценностями тестируемой функции.
-
And иBut: эти ключевые слова можно использовать для замены указанных выше ключевых слов шага, когда имеется несколько шагов одного типа.
Огурец на самом деле не различает эти ключевые слова, однако они все еще существуют, чтобы сделать функцию более удобочитаемой и соответствовать структуре BDD.
3. Реализация Cucumber-JVM
Изначально Cucumber был написан на Ruby и был портирован на Java с реализацией Cucumber-JVM, которая является темой этого раздела.
3.1. Maven Зависимости
Чтобы использовать Cucumber-JVM в проекте Maven, в POM должна быть включена следующая зависимость:
info.cukes
cucumber-java
1.2.4
test
Чтобы облегчить тестирование JUnit с помощью Cucumber, нам нужна еще одна зависимость:
info.cukes
cucumber-junit
1.2.4
В качестве альтернативы, мы можем использовать другой артефакт, чтобы воспользоваться лямбда-выражениями в Java 8, которые не будут рассмотрены в этом руководстве.
3.2. Определения шагов
Сценарии корнишона были бы бесполезны, если бы они не были переведены в действия, и именно здесь вступают в игру определения шагов. По сути, определение шага - это аннотированный Java-метод с прикрепленным шаблоном, задачей которого является преобразование шагов Gherkin в виде простого текста в исполняемый код. После анализа документа объекта Cucumber выполнит поиск определений шагов, которые соответствуют предварительно заданным шагам корнишона для выполнения.
Чтобы было понятнее, давайте рассмотрим следующий шаг:
Given I have registered a course in example
И определение шага:
@Given("I have registered a course in example")
public void verifyAccount() {
// method implementation
}
Когда Cucumber прочитает данный шаг, он будет искать определения шагов, чьи аннотирующие шаблоны соответствуют тексту корнишона. На нашей иллюстрации обнаружено, что методtestMethod соответствует, и затем выполняется его код, в результате чего на консоли выводится строкаLet me in!.
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 для работы с Cucumber
Чтобы JUnit знал о Cucumber и читал файлы функций во время работы, классCucumber должен быть объявлен какRunner. Нам также нужно сообщить JUnit место для поиска файлов объектов и определения шагов.
@RunWith(Cucumber.class)
@CucumberOptions(features = "classpath:Feature")
public class CucumberTest {
}
Как видите, элементfeatures вCucumberOption находит файл функций, созданный ранее. Другой важный элемент, называемыйglue, предоставляет пути к определениям шагов. Однако, если тестовый набор и определения шагов находятся в том же пакете, что и в этом руководстве, этот элемент может быть удален.
4.3. Написание определений шагов
Когда Cucumber анализирует шаги, он будет искать методы, помеченные ключевыми словами Gherkin, чтобы найти соответствующие определения шагов. В этом руководстве эти определения шагов определены в классе в том же пакете сCucumberTest.
Ниже приведен метод, который полностью соответствует шагу корнишона. Метод будет использоваться для публикации данных в веб-службе REST:
@When("^users upload data on a project$")
public void usersUploadDataOnAProject() throws IOException {
}
А вот метод, соответствующий шагу корнишона, который получает аргумент из текста, который будет использоваться для получения информации от веб-службы REST:
@When("^users want to get information on the (.+) project$")
public void usersGetInformationOnAProject(String projectName) throws IOException {
}
Как видите, методusersGetInformationOnAProject принимает аргументString, который является именем проекта. Этот аргумент объявлен(.+) в аннотации, и здесь он соответствуетCucumber в тексте шага.
Рабочий код для обоих вышеуказанных методов будет представлен в следующем разделе.
4.4. Создание и выполнение тестов
Сначала мы начнем со структуры JSON, чтобы проиллюстрировать данные, загруженные на сервер с помощью запроса POST и загруженные на клиент с помощью GET. Эта структура сохраняется в полеjsonString и показана ниже:
{
"testing-framework": "cucumber",
"supported-language":
[
"Ruby",
"Java",
"Javascript",
"PHP",
"Python",
"C++"
],
"website": "cucumber.io"
}
Чтобы продемонстрировать REST API, в игру вступает сервер WireMock:
WireMockServer wireMockServer = new WireMockServer(options().dynamicPort());
Кроме того, в этом руководстве будет использоваться API-интерфейс Apache HttpClient для представления клиента, используемого для подключения к серверу:
CloseableHttpClient httpClient = HttpClients.createDefault();
Теперь перейдем к написанию кода тестирования в рамках определений шагов. Сначала мы сделаем это для методаusersUploadDataOnAProject.
Сервер должен быть запущен до того, как клиент подключится к нему:
wireMockServer.start();
Использование API WireMock для заглушки службы REST:
configureFor("localhost", wireMockServer.port());
stubFor(post(urlEqualTo("/create"))
.withHeader("content-type", equalTo("application/json"))
.withRequestBody(containing("testing-framework"))
.willReturn(aResponse().withStatus(200)));
Теперь отправьте на сервер запрос POST с содержимым, взятым из поляjsonString, объявленного выше:
HttpPost request = new HttpPost("http://localhost:" + wireMockServer.port() + "/create");
StringEntity entity = new StringEntity(jsonString);
request.addHeader("content-type", "application/json");
request.setEntity(entity);
HttpResponse response = httpClient.execute(request);
Следующий код подтверждает, что запрос POST был успешно получен и обработан:
assertEquals(200, response.getStatusLine().getStatusCode());
verify(postRequestedFor(urlEqualTo("/create"))
.withHeader("content-type", equalTo("application/json")));
Сервер должен остановиться после использования:
wireMockServer.stop();
Второй метод, который мы здесь реализуем, -usersGetInformationOnAProject(String projectName). Как и в первом тесте, нам нужно запустить сервер, а затем заглушить службу REST:
wireMockServer.start();
configureFor("localhost", wireMockServer.port());
stubFor(get(urlEqualTo("/projects/cucumber"))
.withHeader("accept", equalTo("application/json"))
.willReturn(aResponse().withBody(jsonString)));
Отправка запроса GET и получение ответа:
HttpGet request = new HttpGet("http://localhost:" + wireMockServer.port() + "/projects/" + projectName.toLowerCase());
request.addHeader("accept", "application/json");
HttpResponse httpResponse = httpClient.execute(request);
Мы преобразуем переменнуюhttpResponse вString с помощью вспомогательного метода:
String responseString = convertResponseToString(httpResponse);
Вот реализация этого вспомогательного метода преобразования:
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;
}
Следующее проверяет весь процесс:
assertThat(responseString, containsString("\"testing-framework\": \"cucumber\""));
assertThat(responseString, containsString("\"website\": \"cucumber.io\""));
verify(getRequestedFor(urlEqualTo("/projects/cucumber"))
.withHeader("accept", equalTo("application/json")));
Наконец, остановите сервер, как описано ранее.
5. Параллельное выполнение функций
Иногда нам нужно запускать функции параллельно, чтобы ускорить процесс тестирования.
We can use the cucumber-jvm-parallel-plugin to create a separate runner for each feature/scenario. Затем мы настроимmaven-failsafe-plugin для параллельного выполнения полученных бегунов.
Во-первых, нам нужно добавитьcucumber-jvm-parallel-plugin to ourpom.xml:
com.github.temyers
cucumber-jvm-parallel-plugin
5.0.0
generateRunners
generate-test-sources
generateRunners
com.example.rest.cucumber
src/test/resources/Feature/
SCENARIO
Мы можем легко настроитьcucumber-jvm-parallel-plugin, поскольку он имеет несколько параметров. Вот те, которые мы использовали:
-
glue.package: (обязательно) наш пакет интеграционных тестов
-
featuresDirectory: путь к каталогу содержит наши файлы функций
-
parallelScheme: может быть SCENARIO или FEATURE, где SCENARIO генерирует одного бегуна для каждого сценария, а FEATURE генерирует одного бегуна для каждой функции.
Теперь мы такжеconfigure the maven-failsafe-plugin to execute resulting runners in parallel:
maven-failsafe-plugin
2.19.1
classes
2
integration-test
verify
Обратите внимание, что:
-
parallel: может бытьclasses, methods или обоими - в нашем случаеclasses заставит каждый тестовый класс запускаться в отдельном потоке
-
threadCount: указывает, сколько потоков должно быть выделено для этого выполнения
Затем для запуска тестов мы можем использовать команду:
mvn verify
Мы заметим, что каждый сценарий выполняется в отдельном потоке.
6. Заключение
В этом руководстве были рассмотрены основы Cucumber и то, как эта среда использует специфичный для домена Gherkin язык для тестирования REST API.
Реализацию всех этих примеров и фрагментов кода можно найти вa GitHub project.