Тестирование API REST с огурцом

Тестирование 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.