Testando uma API REST com JBehave

Testando uma API REST com JBehave

1. Introdução

Neste artigo, daremos uma olhada rápida emJBehave e, em seguida, nos concentraremos em testar uma API REST de uma perspectiva BDD.

2. JBehave e BDD

JBehave é uma estrutura de Desenvolvimento Orientado a Comportamentos. Ele pretende fornecer uma maneira intuitiva e acessível para testes de aceitação automatizados.

Se você não está familiarizado com o BDD, é uma boa ideia começar comthis article, covering on another BDD testing framework – Cucumber, no qual apresentamos a estrutura e os recursos gerais do BDD.

Semelhante a outras estruturas do BDD, o JBehave adota os seguintes conceitos:

  • História - representa um incremento executável automaticamente da funcionalidade do negócio, compreende um ou mais cenários

  • Cenários - representam exemplos concretos do comportamento do sistema

  • Etapas - representam o comportamento real usando palavras-chave clássicas do BDD:Given,WheneThen

Um cenário típico seria:

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

Cada etapa do cenário corresponde a uma anotação no JBehave:

  • @Given: inicia o contexto

  • @When: faça a ação

  • @Then: teste o resultado esperado

3. Dependência do Maven

Para fazer uso do JBehave em nosso projeto maven, a dependênciajbehave-core deve ser incluída nopom:


    org.jbehave
    jbehave-core
    4.1
    test

4. Um Exemplo Rápido

Para usar o JBehave, precisamos seguir as seguintes etapas:

  1. Escreva uma história de usuário

  2. Mapear etapas da história do usuário para o código Java

  3. Configurar histórias de usuário

  4. Execute testes JBehave

  5. Revisar resultados

4.1. História

Vamos começar com a seguinte história simples: "como um usuário, quero aumentar um contador, para que possa aumentar o valor do contador em 1".

Podemos definir a história em um arquivo.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. Etapas de mapeamento

Dadas as etapas, vamos implementar isso em 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);
    }
}

Lembre-se de quethe value in the annotations must accurately match the description.

4.3. Configurando nossa história

Para executar as etapas, precisamos montar o cenário da nossa história:

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 storyPaths() {
        return Arrays.asList("increase.story");
    }

}

EmstoryPaths(), fornecemos nosso caminho de arquivo.story para ser analisado pelo JBehave. A implementação das etapas reais é fornecida emstepsFactory(). Então, emconfiguration(), o carregador de história e o relatório de história são configurados corretamente.

Agora que temos tudo pronto, podemos começar nossa história simplesmente executando:mvn clean test.

4.4. Revisão dos resultados do teste

Podemos ver o resultado do nosso teste no console. Como nossos testes foram aprovados com êxito, a saída seria a mesma com nossa história:

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

Se esquecermos de implementar qualquer etapa do cenário, o relatório nos informará. Digamos que não implementamos a etapa@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
}

O relatório diria que@When uma etapa está pendente e, por causa disso, a etapa@Then não seria executada.

E se o passo @Then falhar? Podemos identificar o erro imediatamente no relatório:

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)

5. Testando API REST

Agora entendemos o básico deJBhave; veremos como testar uma API REST com ela. Nossos testes serão baseados em nossoprevious article discussing how to test REST API with Java.

Nesse artigo, testamosGitHub REST APIe focamos principalmente no código de resposta HTTP, cabeçalhos e carga útil. Para simplificar, podemos escrevê-los em três histórias separadas, respectivamente.

5.1. Testando o código de status

A história:

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

Os passos:

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);
    }

    //...
}

Observe como, na implementação das etapas,we used the parameter injection feature. Os argumentos extraídos do candidato da etapa são correspondidos apenas seguindo a ordem natural dos parâmetros no método Java anotado.

Além disso, os parâmetros nomeados anotados são suportados:

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

5.2. Testando o tipo de mídia

Esta é uma história simples de teste de tipo MIME:

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

E aqui estão os passos:

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. Testando o Payload JSON

Então a última história:

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

E a implementação de etapas diretas simples:

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. Sumário

Neste artigo, apresentamos brevemente o JBehave e implementamos testes de API REST no estilo BDD.

Quando comparado ao nosso código de teste Java simples, o código implementado com o JBehave parece muito claro e intuitivo e o relatório de resultados do teste parece muito mais elegante.

Como sempre, o código de exemplo pode ser encontrado emthe Github project.