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:
- Scrivi una user story
- Mappare i passaggi dalla user story al codice Java
- Configura le storie degli utenti
- Esegui i test JBehave
- 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.