«1. Введение

В этой статье мы кратко рассмотрим JBehave, а затем сосредоточимся на тестировании REST API с точки зрения BDD.

2. JBehave и BDD

JBehave — это среда разработки, основанная на поведении. Он призван предоставить интуитивно понятный и доступный способ автоматизированного приемочного тестирования.

Если вы не знакомы с BDD, рекомендуется начать с этой статьи, рассказывая о другой среде тестирования BDD — Cucumber, в которой мы представляем общую структуру и функции BDD.

Подобно другим средам BDD, JBehave использует следующие концепции:

    История — представляет собой автоматически исполняемое расширение бизнес-функций, содержит один или несколько сценариев. Сценарии — представляют конкретные примеры поведения системы. Шаги — представить реальное поведение с помощью классических ключевых слов BDD: Given, When and Then

Типичный сценарий:

Given a precondition
When an event occurs
Then the outcome should be captured

Каждый шаг в сценарии соответствует аннотации в JBehave:

    @Given: инициировать контекст @When: выполнить действие @Then: проверить ожидаемый результат

3. Зависимость Maven

Чтобы использовать JBehave в нашем проекте maven, зависимость jbehave-core должна быть включена в pom:

<dependency>
    <groupId>org.jbehave</groupId>
    <artifactId>jbehave-core</artifactId>
    <version>4.1</version>
    <scope>test</scope>
</dependency>

4. Краткий пример

Чтобы использовать JBehave, нам нужно выполнить следующие шаги:

  1. Write a user story
  2. Map steps from the user story to Java code
  3. Configure user stories
  4. Run JBehave tests
  5. Review results

4.1. История

Давайте начнем со следующей простой истории: «Как пользователь, я хочу увеличить счетчик, чтобы я мог увеличить значение счетчика на 1».

Мы можем определить историю в файле .story:

Scenario: when a user increases a counter, its value is increased by 1

Given a counter
And the counter has any integral value
When the user increases the counter
Then the value of the counter must be 1 greater than previous value

4.2. Шаги сопоставления

Учитывая шаги, давайте реализуем это на Java:

public class IncreaseSteps {
    private int counter;
    private int previousValue;

    @Given("a counter")
    public void aCounter() {
    }

    @Given("the counter has any integral value")
    public void counterHasAnyIntegralValue() {
        counter = new Random().nextInt();
        previousValue = counter;
    }

    @When("the user increases the counter")
    public void increasesTheCounter() {
        counter++;
    }

    @Then("the value of the counter must be 1 greater than previous value")
    public void theValueOfTheCounterMustBe1Greater() {
        assertTrue(1 == counter - previousValue);
    }
}

Помните, что значение в аннотациях должно точно соответствовать описанию.

4.3. Настройка нашей истории

Чтобы выполнить шаги, нам нужно настроить сцену для нашей истории:

public class IncreaseStoryLiveTest extends JUnitStories {

    @Override
    public Configuration configuration() {
        return new MostUsefulConfiguration()
          .useStoryLoader(new LoadFromClasspath(this.getClass()))
          .useStoryReporterBuilder(new StoryReporterBuilder()
            .withCodeLocation(codeLocationFromClass(this.getClass()))
            .withFormats(CONSOLE));
    }

    @Override
    public InjectableStepsFactory stepsFactory() {
        return new InstanceStepsFactory(configuration(), new IncreaseSteps());
    }

    @Override
    protected List<String> storyPaths() {
        return Arrays.asList("increase.story");
    }

}

В storyPaths() мы указываем путь к нашему файлу .story для анализа с помощью JBehave. Фактическая реализация шагов обеспечивается в stepsFactory(). Затем в конфигурации () загрузчик истории и отчет истории правильно настроены.

Теперь, когда у нас все готово, мы можем начать нашу историю, просто запустив: mvn clean test.

4.4. Просмотр результатов теста

Мы можем увидеть результат нашего теста в консоли. Поскольку наши тесты прошли успешно, вывод будет таким же, как и в нашей истории:

Scenario: when a user increases a counter, its value is increased by 1
Given a counter
And the counter has any integral value
When the user increases the counter
Then the value of the counter must be 1 greater than previous value

Если мы забудем реализовать какой-либо шаг сценария, отчет сообщит нам об этом. Скажем, мы не реализовали шаг @When:

Scenario: when a user increases a counter, its value is increased by 1
Given a counter
And the counter has any integral value
When the user increases the counter (PENDING)
Then the value of the counter must be 1 greater than previous value (NOT PERFORMED)
@When("the user increases the counter")
@Pending
public void whenTheUserIncreasesTheCounter() {
    // PENDING
}

В отчете будет указано, что шаг @When находится на рассмотрении, и поэтому шаг @Then не будет выполнен.

Scenario: when a user increases a counter, its value is increased by 1
Given a counter
And the counter has any integral value
When the user increases the counter
Then the value of the counter must be 1 greater than previous value (FAILED)
(java.lang.AssertionError)

Что, если наш шаг @Then завершится ошибкой? Ошибку сразу видно из отчета:

5. Тестирование REST API

Теперь мы познакомились с основами JBhave; мы увидим, как протестировать с его помощью REST API. Наши тесты будут основаны на нашей предыдущей статье, в которой обсуждалось, как тестировать REST API с Java.

В этой статье мы протестировали GitHub REST API и в основном сосредоточились на коде ответа HTTP, заголовках и полезной нагрузке. Для простоты мы можем записать их в три отдельные истории соответственно.

5.1. Тестирование кода состояния

Scenario: when a user checks a non-existent user on github, github would respond 'not found'

Given github user profile api
And a random non-existent username
When I look for the random user via the api
Then github respond: 404 not found

When I look for eugenp1 via the api
Then github respond: 404 not found

When I look for eugenp2 via the api
Then github respond: 404 not found

История:

public class GithubUserNotFoundSteps {

    private String api;
    private String nonExistentUser;
    private int githubResponseCode;

    @Given("github user profile api")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @Given("a random non-existent username")
    public void givenANonexistentUsername() {
        nonExistentUser = randomAlphabetic(8);
    }

    @When("I look for the random user via the api")
    public void whenILookForTheUserViaTheApi() throws IOException {
        githubResponseCode = getGithubUserProfile(api, nonExistentUser)
          .getStatusLine()
          .getStatusCode();
    }

    @When("I look for $user via the api")
    public void whenILookForSomeNonExistentUserViaTheApi(
      String user) throws IOException {
        githubResponseCode = getGithubUserProfile(api, user)
          .getStatusLine()
          .getStatusCode();
    }

    @Then("github respond: 404 not found")
    public void thenGithubRespond404NotFound() {
        assertTrue(SC_NOT_FOUND == githubResponseCode);
    }

    //...
}

Шаги:

Обратите внимание, как в реализации шагов мы использовали функцию внедрения параметров. Аргументы, извлеченные из шага-кандидата, просто сопоставляются в естественном порядке с параметрами в аннотированном методе Java.

@When("I look for $username via the api")
public void whenILookForSomeNonExistentUserViaTheApi(
  @Named("username") String user) throws IOException

Также поддерживаются аннотированные именованные параметры:

5.2. Тестирование типа носителя

Scenario: when a user checks a valid user's profile on github, github would respond json data

Given github user profile api
And a valid username
When I look for the user via the api
Then github respond data of type json

Вот простая история тестирования типа MIME:

public class GithubUserResponseMediaTypeSteps {

    private String api;
    private String validUser;
    private String mediaType;

    @Given("github user profile api")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @Given("a valid username")
    public void givenAValidUsername() {
        validUser = "eugenp";
    }

    @When("I look for the user via the api")
    public void whenILookForTheUserViaTheApi() throws IOException {
        mediaType = ContentType
          .getOrDefault(getGithubUserProfile(api, validUser).getEntity())
          .getMimeType();
    }

    @Then("github respond data of type json")
    public void thenGithubRespondDataOfTypeJson() {
        assertEquals("application/json", mediaType);
    }
}

А вот шаги:

5.3. Тестирование полезной нагрузки JSON

Scenario: when a user checks a valid user's profile on github, github's response json should include a login payload with the same username

Given github user profile api
When I look for eugenp via the api
Then github's response contains a 'login' payload same as eugenp

Затем последняя история:

public class GithubUserResponsePayloadSteps {

    private String api;
    private GitHubUser resource;

    @Given("github user profile api")
    public void givenGithubUserProfileApi() {
        api = "https://api.github.com/users/%s";
    }

    @When("I look for $user via the api")
    public void whenILookForEugenpViaTheApi(String user) throws IOException {
        HttpResponse httpResponse = getGithubUserProfile(api, user);
        resource = RetrieveUtil.retrieveResourceFromResponse(httpResponse, GitHubUser.class);
    }

    @Then("github's response contains a 'login' payload same as $username")
    public void thenGithubsResponseContainsAloginPayloadSameAsEugenp(String username) {
        assertThat(username, Matchers.is(resource.getLogin()));
    }
}

И реализация простых прямых шагов:

6. Резюме

В этой статье мы кратко представили JBehave и реализовали BDD- стиль тестов REST API.

По сравнению с нашим простым тестовым кодом Java, код, реализованный с помощью JBehave, выглядит более понятным и интуитивно понятным, а отчет о результатах тестирования выглядит более элегантным.