REST API-testing med agurk

1. Oversikt

Denne opplæringen gir en introduksjon til Agurk, et ofte brukt verktøy for brukertesttesting, og hvordan du bruker det i REST API-tester.

I tillegg, for å gjøre artikkelen selvstendig og uavhengig av eventuelle eksterne REST-tjenester, vil vi bruke WireMock, et stubbende og spottende nettbibliotek. Hvis du vil vite mer om dette biblioteket, kan du se innledningen til WireMock.

2. Gherkin - Språket av agurk

Agurk er et testrammeverk som støtter Behavior Driven Development (BDD), som lar brukerne definere applikasjonsoperasjoner i ren tekst. Det fungerer basert på DSL (Gherkin Domain Specific Language). Denne enkle, men kraftige syntaksen til Gherkin lar utviklere og testere skrive komplekse tester og samtidig holde den forståelig for selv ikke-tekniske brukere.

2.1. Introduksjon til agurk

Gherkin er et linjeorientert språk som bruker linjeendelser, fordypninger og nøkkelord for å definere dokumenter. Hver ikke-tomme linje begynner vanligvis med et nøkkelord fra agurk, etterfulgt av en vilkårlig tekst, som vanligvis er en beskrivelse av nøkkelordet.

Hele strukturen må skrives inn i en fil med trekk utvidelse som skal gjenkjennes av agurk.

Her er et enkelt Gherkin-dokumenteksempel:

Feature: En kort beskrivelse av ønsket funksjonalitet Scenario: En forretningssituasjon Gitt en forutsetning Og en annen forutsetning Når en hendelse skjer Og en annen hendelse skjer også Da oppnås et testbart resultat Og noe annet blir også fullført

I de følgende avsnittene vil vi beskrive et par av de viktigste elementene i en agurkstruktur.

2.2. Trekk

Vi bruker en Gherkin-fil for å beskrive en applikasjonsfunksjon som må testes. Filen inneholder Trekk nøkkelord helt i begynnelsen, fulgt opp av funksjonsnavnet på samme linje og en valgfri beskrivelse som kan strekke seg over flere linjer under.

Agurkparser hopper over hele teksten, bortsett fra Trekk nøkkelord, og inkluderer det kun for dokumentasjon.

2.3. Scenarier og trinn

En agurkestruktur kan bestå av ett eller flere scenarier, anerkjent av Scenario nøkkelord. Et scenario er i utgangspunktet en test som lar brukerne validere en evne til applikasjonen. Den skal beskrive en innledende kontekst, hendelser som kan skje og forventede resultater skapt av disse hendelsene.

Disse tingene gjøres ved hjelp av trinn, identifisert av et av de fem nøkkelordene: Gitt, Når, Deretter, Og, og Men.

  • Gitt: Dette trinnet er å sette systemet i en veldefinert tilstand før brukere begynner å kommunisere med applikasjonen. EN Gitt klausul kan anses som en forutsetning for brukssaken.
  • Når: A Når trinn brukes til å beskrive en hendelse som skjer med applikasjonen. Dette kan være en handling som er utført av brukere, eller en hendelse utløst av et annet system.
  • Deretter: Dette trinnet er å spesifisere et forventet resultat av testen. Resultatet skal være relatert til forretningsverdiene til funksjonen som testes.
  • Og og Men: Disse nøkkelordene kan brukes til å erstatte trinnnøkkelordene ovenfor når det er flere trinn av samme type.

Agurk skiller faktisk ikke disse nøkkelordene, men de er fremdeles der for å gjøre funksjonen mer lesbar og i samsvar med BDD-strukturen.

3. Agurk-JVM-implementering

Agurk ble opprinnelig skrevet i Ruby og har blitt portet til Java med Cucumber-JVM-implementering, som er gjenstand for denne delen.

3.1. Maven avhengigheter

For å kunne bruke Cucumber-JVM i et Maven-prosjekt, må følgende avhengighet inkluderes i POM:

 io. agurk agurk-java 6.8.0 test 

For å lette JUnit-testing med agurk, må vi ha en avhengighet til:

 io. agurk agurk-junit 6.8.0 

Alternativt kan vi bruke en annen gjenstand for å dra nytte av lambdauttrykk i Java 8, som ikke vil bli dekket i denne opplæringen.

3.2. Trinndefinisjoner

Agurkascenarier ville være ubrukelige hvis de ikke ble oversatt til handlinger, og det er her trinndefinisjoner spiller inn. I utgangspunktet er en trinndefinisjon en kommentert Java-metode med et vedlagt mønster hvis jobb er å konvertere Gherkin-trinn i ren tekst til kjørbar kode. Etter å ha analysert et funksjonsdokument, vil Agurk søke etter trinndefinisjoner som samsvarer med forhåndsdefinerte Gherkin-trinn å utføre.

For å gjøre det tydeligere, la oss ta en titt på følgende trinn:

Gitt at jeg har registrert et kurs i Baeldung

Og en trinndefinisjon:

@Given ("Jeg har registrert et kurs i Baeldung") offentlig ugyldig verifisereAccount () {// metodeimplementering}

Når agurk leser det gitte trinnet, vil det være på jakt etter trinndefinisjoner hvis merkemønster samsvarer med agurketeksten.

4. Opprette og kjøre tester

4.1. Skrive en funksjonsfil

La oss begynne med å erklære scenarier og trinn i en fil med navnet som slutter på .trekk Utvidelse:

Funksjon: Testing av REST API Brukere skal kunne sende inn GET- og POST-forespørsler til en webtjeneste, representert av WireMock Scenario: Dataopplasting til en webtjeneste Når brukere laster opp data på et prosjekt, skal serveren håndtere det og returnere en suksessstatus Scenario: Datainnhenting fra en webtjeneste Når brukere vil få informasjon om 'Agurk' -prosjektet, returneres de forespurte dataene

Vi lagrer nå denne filen i en katalog som heter Trekk, under forutsetning av at katalogen blir lastet inn i klassestien ved kjøretid, f.eks. src / main / resources.

4.2. Konfigurere JUnit til å fungere med agurk

For at JUnit skal være oppmerksom på agurk og lese funksjonsfiler når du kjører, er Agurk klasse må erklæres som Løper. Vi må også fortelle JUnit stedet for å søke etter funksjonsfiler og trinndefinisjoner.

@RunWith (Cucumber.class) @CucumberOptions (features = "classpath: Feature") offentlig klasse CucumberIntegrationTest {}

Som du kan se, er funksjoner element av Agurkalternativ lokaliserer funksjonsfilen som ble opprettet før. Et annet viktig element, kalt lim, gir veier til trinndefinisjoner. Imidlertid, hvis testdefinisjonen og trinndefinisjonene er i samme pakke som i denne opplæringen, kan dette elementet bli droppet.

4.3. Skrive trinndefinisjoner

Når Agurk analyserer trinn, vil den søke etter metoder som er merket med Gherkin-nøkkelord for å finne samsvarende trinndefinisjoner.

Et trinns definisjons uttrykk kan enten være et regulært uttrykk eller et agurkuttrykk. I denne opplæringen bruker vi agurkuttrykk.

Følgende er en metode som samsvarer fullt ut med et agurkesteg. Metoden vil bli brukt til å legge ut data til en REST-nettjeneste:

@When ("brukere laster opp data på et prosjekt") offentlige ugyldige brukereUploadDataOnAProject () kaster IOException {}

Og her er en metode som samsvarer med et Gherkin-trinn og tar et argument fra teksten, som vil bli brukt til å få informasjon fra en REST-nettjeneste:

@When ("brukere vil få informasjon om {string} prosjektet") offentlige ugyldige brukereGetInformationOnAProject (String projectName) kaster IOException {}

Som du kan se, er usersGetInformationOnAProject metoden tar en String argument, som er prosjektnavnet. Dette argumentet er erklært av {string} i kommentaren og over her tilsvarer den Agurk i trinnteksten.

Alternativt kan vi bruke et vanlig uttrykk:

@When ("^ brukere ønsker å få informasjon om '(. +)' Prosjektet $") offentlige ugyldige brukereGetInformationOnAProject (String projectName) kaster IOException {}

Merk at ‘^' og ‘$' som angir start og slutt på regex tilsvarende. Mens ‘(.+)' tilsvarer String parameter.

Vi gir arbeidskoden for begge metodene ovenfor i neste avsnitt.

4.4. Opprette og kjøre tester

Først begynner vi med en JSON-struktur for å illustrere dataene som er lastet opp til serveren ved en POST-forespørsel, og lastet ned til klienten ved hjelp av en GET. Denne strukturen er lagret i jsonString felt, og vist nedenfor:

{"testing-framework": "agurk", "støttet språk": ["Ruby", "Java", "Javascript", "PHP", "Python", "C ++"], "website": "agurk. io "}

For å demonstrere et REST API bruker vi en WireMock-server:

WireMockServer wireMockServer = ny WireMockServer (opsjoner (). DynamicPort ());

I tillegg bruker vi Apache HttpClient API til å representere klienten som brukes til å koble til serveren:

CloseableHttpClient httpClient = HttpClients.createDefault ();

La oss nå gå videre til å skrive testkode innen trinndefinisjoner. Vi vil gjøre dette for brukereUploadDataOnAProject metoden først.

Serveren skal kjøre før klienten kobler seg til den:

wireMockServer.start ();

Ved hjelp av WireMock API for å stoppe REST-tjenesten:

configureFor ("localhost", wireMockServer.port ()); stubFor (post (urlEqualTo ("/ create")) .withHeader ("content-type", equalTo ("application / json")) .withRequestBody (inneholder ("testing-framework")) .willReturn (aResponse (). withStatus (200)));

Send nå en POST-forespørsel med innholdet hentet fra jsonString felt erklært overfor serveren:

HttpPost-forespørsel = ny HttpPost ("// localhost:" + wireMockServer.port () + "/ create"); StringEntity-enhet = ny StringEntity (jsonString); request.addHeader ("content-type", "application / json"); request.setEntity (enhet); HttpResponse respons = httpClient.execute (forespørsel);

Følgende kode hevder at POST-forespørselen er mottatt og behandlet:

assertEquals (200, respons.getStatusLine (). getStatusCode ()); verifiser (postRequestedFor (urlEqualTo ("/ create")) .withHeader ("content-type", equalTo ("application / json")));

Serveren skal stoppe etter bruk:

wireMockServer.stop ();

Den andre metoden vi vil implementere her er usersGetInformationOnAProject (String projectName). I likhet med den første testen, må vi starte serveren og deretter stanse REST-tjenesten:

wireMockServer.start (); configureFor ("localhost", wireMockServer.port ()); stubFor (get (urlEqualTo ("/ projects / agurk")) .withHeader ("accept", equalTo ("application / json")) .willReturn (aResponse (). withBody (jsonString)));

Sende inn en GET-forespørsel og motta et svar:

HttpGet forespørsel = ny HttpGet ("// localhost:" + wireMockServer.port () + "/ projects /" + projectName.toLowerCase ()); request.addHeader ("godta", "applikasjon / json"); HttpResponse httpResponse = httpClient.execute (forespørsel);

Vi vil konvertere httpResponse variabel til en String ved hjelp av en hjelpermetode:

String responseString = convertResponseToString (httpResponse);

Her er implementeringen av den konverteringshjelpermetoden:

private String convertResponseToString (HttpResponse respons) kaster IOException {InputStream responseStream = respons.getEntity (). getContent (); Skannerskanner = ny skanner (responseStream, "UTF-8"); StrengresponsString = scanner.useDelimiter ("\ Z"). Neste (); scanner.close (); return responseString; }

Følgende verifiserer hele prosessen:

assertThat (responseString, containString ("\" testing-framework \ ": \" agurk \ "")); assertThat (responseString, containString ("\" nettsted \ ": \" agurk.io \ "")); verifisere (getRequestedFor (urlEqualTo ("/ projects / agurk")) .withHeader ("accept", equalTo ("application / json")));

Til slutt, stopp serveren som beskrevet tidligere.

5. Kjørefunksjoner parallelt

Cucumber-JVM støtter naturlig utføring av parallell test på tvers av flere tråder. Vi bruker JUnit sammen med Maven Failsafe-plugin for å utføre løpere. Alternativt kan vi bruke Maven Surefire.

JUnit kjører funksjonsfilene parallelt i stedet for scenarier, noe som betyr alle scenariene i en funksjonsfil vil bli utført av samme tråd.

La oss nå legge til plugin-konfigurasjonen:

 maven-failsafe-plugin $ {maven-failsafe-plugin.version} CucumberIntegrationTest.java metoder 2 integrasjon-test bekreft 

Noter det:

  • parallell: kan være klasser, metoder, eller begge deler - i vårt tilfelle, klasser vil gjøre at hver testklasse kjøres i en egen tråd
  • threadCount: indikerer hvor mange tråder som skal tildeles for denne utførelsen

Det er alt vi trenger å gjøre for å kjøre agurkfunksjonene parallelt.

6. Konklusjon

I denne opplæringen dekket vi det grunnleggende om agurk og hvordan dette rammeverket bruker det gherkin domenespesifikke språket for å teste et REST API.

Som vanlig er alle kodeeksempler vist i denne opplæringen tilgjengelig på GitHub.