Test di un'API REST con JBehave

1. Introduzione

In questo articolo daremo una rapida occhiata a JBehave, quindi ci concentreremo sul test di un'API REST da una prospettiva BDD.

2. JBehave e BDD

JBehave è un framework di sviluppo guidato dal comportamento. Si propone di fornire un modo intuitivo e accessibile per i test di accettazione automatizzati.

Se non hai familiarità con BDD, è una buona idea iniziare con questo articolo, trattando un altro framework di test BDD - Cucumber, in cui stiamo introducendo la struttura e le caratteristiche BDD generali.

Simile ad altri framework BDD, JBehave adotta i seguenti concetti:

  • Storia: rappresenta un incremento eseguibile automaticamente della funzionalità aziendale, comprende uno o più scenari
  • Scenari: rappresentano esempi concreti del comportamento del sistema
  • Passaggi: rappresentano il comportamento effettivo utilizzando le parole chiave BDD classiche: Dato , Quando e Allora

Uno scenario tipico sarebbe:

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

Ogni passaggio nello scenario corrisponde a un'annotazione in JBehave:

  • @Given : avvia il contesto
  • @ Quando : esegui l'azione
  • @Allora : verifica il risultato atteso

3. Dipendenza da Maven

Per utilizzare JBehave nel nostro progetto maven, la dipendenza jbehave-core dovrebbe essere inclusa nel pom :

 org.jbehave jbehave-core 4.1 test 

4. Un rapido esempio

Per utilizzare JBehave, dobbiamo seguire i seguenti passaggi:

  1. Scrivi una user story
  2. Mappare i passaggi dalla user story al codice Java
  3. Configura le storie degli utenti
  4. Esegui i test JBehave
  5. Esamina i risultati

4.1. Storia

Cominciamo con la seguente semplice storia: "come utente, voglio aumentare un contatore, in modo da poter far aumentare il valore del contatore di 1".

Possiamo definire la storia in un file .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. Passaggi di mappatura

Dati i passaggi, implementiamo questo in 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); } }

Ricorda che il valore nelle annotazioni deve corrispondere accuratamente alla descrizione .

4.3. Configurare la nostra storia

Per eseguire i passaggi, dobbiamo preparare il palcoscenico per la nostra storia:

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

In storyPaths () , forniamo il percorso del nostro file .story che deve essere analizzato da JBehave. L'implementazione dei passaggi effettivi è fornita in stepsFactory () . Quindi in configuration () , il caricatore della storia e il report della storia sono configurati correttamente.

Ora che abbiamo tutto pronto, possiamo iniziare la nostra storia semplicemente eseguendo: mvn clean test .

4.4. Revisione dei risultati dei test

Possiamo vedere il risultato del nostro test nella console. Poiché i nostri test sono stati superati con successo, l'output sarebbe lo stesso con la nostra storia:

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 dimentichiamo di implementare qualsiasi fase dello scenario, il rapporto ce lo farà sapere. Supponiamo di non aver implementato il passaggio @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 }

Il rapporto indica @Quando un passaggio è in sospeso e, per questo motivo, il passaggio @Then non viene eseguito.

Cosa succede se il nostro passaggio @Then fallisce? Possiamo individuare l'errore subito dal rapporto:

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. Test dell'API REST

Ora abbiamo afferrato le basi di JBhave ; vedremo come testare un'API REST con esso. I nostri test si baseranno sul nostro precedente articolo che discute come testare l'API REST con Java.

In quell'articolo abbiamo testato l'API REST di GitHub e ci siamo concentrati principalmente sul codice di risposta HTTP, sulle intestazioni e sul payload. Per semplicità, possiamo scriverli rispettivamente in tre storie separate.

5.1. Verifica del codice di stato

La storia:

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

I passi:

public class GithubUserNotFoundSteps { private String api; private String nonExistentUser; private int githubResponseCode; @Given("github user profile api") public void givenGithubUserProfileApi() { api = "//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); } //... }

Si noti come, nell'implementazione dei passaggi, abbiamo utilizzato la funzionalità di iniezione dei parametri . Gli argomenti estratti dal candidato passo vengono semplicemente abbinati seguendo l'ordine naturale ai parametri nel metodo Java annotato.

Inoltre, sono supportati parametri denominati annotati:

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

5.2. Verifica del tipo di supporto

Ecco una semplice storia di test di 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

Ed ecco i passaggi:

public class GithubUserResponseMediaTypeSteps { private String api; private String validUser; private String mediaType; @Given("github user profile api") public void givenGithubUserProfileApi() { api = "//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. Test del payload JSON

Poi l'ultima storia:

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 l'implementazione di semplici passaggi dritti:

public class GithubUserResponsePayloadSteps { private String api; private GitHubUser resource; @Given("github user profile api") public void givenGithubUserProfileApi() { api = "//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. Riepilogo

In questo articolo abbiamo introdotto brevemente JBehave e implementato i test API REST in stile BDD.

Rispetto al nostro semplice codice di test Java, il codice implementato con JBehave sembra molto chiaro e intuitivo e il report dei risultati del test sembra molto più elegante.

Come sempre, il codice di esempio può essere trovato nel progetto Github.