REST API-test med agurk

1. Oversigt

Denne vejledning giver en introduktion til Agurk, et almindeligt anvendt værktøj til test af brugeraccept, og hvordan man bruger det i REST API-tests.

Derudover bruger vi WireMock, et stubbende og hånende webtjenestebibliotek for at gøre artiklen selvstændig og uafhængig af eventuelle eksterne REST-tjenester. Hvis du vil vide mere om dette bibliotek, se introduktionen til WireMock.

2. Agurk - agurkens sprog

Agurk er en testramme, der understøtter Behavior Driven Development (BDD), der giver brugerne mulighed for at definere applikationshandlinger i almindelig tekst. Det fungerer baseret på Gherkin Domain Specific Language (DSL). Denne enkle, men kraftfulde syntaks af Gherkin lader udviklere og testere skrive komplekse tests, mens de holdes forståelige for selv ikke-tekniske brugere.

2.1. Introduktion til agurk

Agurk er et linieorienteret sprog, der bruger linieendelser, fordybninger og nøgleord til at definere dokumenter. Hver ikke-blank linje starter normalt med et agurk-nøgleord, efterfulgt af en vilkårlig tekst, som normalt er en beskrivelse af nøgleordet.

Hele strukturen skal skrives i en fil med funktion udvidelse, der skal anerkendes af agurk.

Her er et simpelt Gherkin-dokumenteksempel:

Funktion: En kort beskrivelse af den ønskede funktionalitet Scenarie: En forretningssituation givet en forudsætning Og en anden forudsætning Når en begivenhed sker Og en anden begivenhed sker også Så opnås et testbart resultat, og noget andet afsluttes også

I de følgende afsnit beskriver vi et par af de vigtigste elementer i en agurkstruktur.

2.2. Funktion

Vi bruger en Gherkin-fil til at beskrive en applikationsfunktion, der skal testes. Filen indeholder Funktion nøgleord helt i starten, efterfulgt af funktionsnavnet på den samme linje og en valgfri beskrivelse, der kan strække sig over flere linjer nedenunder.

Agurkparser springer hele teksten over undtagen Funktion nøgleord og inkluderer det kun med henblik på dokumentation.

2.3. Scenarier og trin

En agurkestruktur kan bestå af et eller flere scenarier, der er anerkendt af Scenarie nøgleord. Et scenario er dybest set en test, der giver brugerne mulighed for at validere en kapacitet i applikationen. Den skal beskrive en indledende kontekst, begivenheder, der kan ske, og forventede resultater skabt af disse begivenheder.

Disse ting udføres ved hjælp af trin identificeret ved et af de fem nøgleord: Givet, Hvornår, Derefter, Ogog Men.

  • Givet: Dette trin er at sætte systemet i en veldefineret tilstand, før brugerne begynder at interagere med applikationen. EN Givet klausul kan betragtes som en forudsætning for brugssagen.
  • Hvornår: A Hvornår trin bruges til at beskrive en begivenhed, der sker med applikationen. Dette kan være en handling, der udføres af brugerne, eller en begivenhed udløst af et andet system.
  • Derefter: Dette trin er at specificere et forventet resultat af testen. Resultatet skal relateres til forretningsværdierne for den funktion, der testes.
  • Og og Men: Disse nøgleord kan bruges til at erstatte ovenstående trin nøgleord, når der er flere trin af samme type.

Agurk skelner faktisk ikke mellem disse nøgleord, men de er stadig der for at gøre funktionen mere læsbar og i overensstemmelse med BDD-strukturen.

3. Implementering af agurk-JVM

Agurk blev oprindeligt skrevet i Ruby og er blevet portet til Java med Cucumber-JVM-implementering, hvilket er genstand for dette afsnit.

3.1. Maven afhængigheder

For at gøre brug af Cucumber-JVM i et Maven-projekt skal følgende afhængighed inkluderes i POM:

 io. agurk agurk-java 6.8.0 test 

For at lette JUnit-test med agurk skal vi have endnu en afhængighed:

 io. agurk agurk-junit 6.8.0 

Alternativt kan vi bruge en anden artefakt til at drage fordel af lambda-udtryk i Java 8, som ikke vil blive dækket af denne vejledning.

3.2. Trindefinitioner

Agurkascenarier ville være ubrugelige, hvis de ikke blev oversat til handlinger, og det er her trindefinitioner kommer i spil. Grundlæggende er en trindefinition en kommenteret Java-metode med et vedhæftet mønster, hvis opgave er at konvertere agurketrin i almindelig tekst til eksekverbar kode. Efter parsing af et funktionsdokument søger agurk efter trindefinitioner, der matcher foruddefinerede agurketrin, der skal udføres.

For at gøre det tydeligere, lad os se på følgende trin:

Da jeg har registreret et kursus i Baeldung

Og en trindefinition:

@Given ("Jeg har registreret et kursus i Baeldung") offentlig ugyldig verificering af konto () {// metodeimplementering}

Når agurk læser det givne trin, vil det lede efter trindefinitioner, hvis annoteringsmønstre matcher agurketeksten.

4. Oprettelse og kørsel af test

4.1. Skrivning af en funktionsfil

Lad os starte med at erklære scenarier og trin i en fil med navnet ender på .funktion udvidelse:

Funktion: Test af en REST API Brugere skal være i stand til at indsende GET- og POST-anmodninger til en webtjeneste, repræsenteret af WireMock Scenario: Dataoverførsel til en webservice Når brugere uploader data på et projekt Så skal serveren håndtere det og returnere en successtatus Scenarie: Data hentning fra en webtjeneste Når brugere ønsker at få oplysninger om 'Agurk' projektet returneres de ønskede data

Vi gemmer nu denne fil i en navngivet mappe Funktion, under forudsætning af, at biblioteket indlæses i klassestien ved kørsel, f.eks. src / main / ressourcer.

4.2. Konfiguration af JUnit til at arbejde med agurk

For at JUnit skal være opmærksom på agurk og læse funktionsfiler, når den kører, er Agurk klasse skal erklæres som Løber. Vi er også nødt til at fortælle JUnit stedet at søge efter funktionsfiler og trindefinitioner.

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

Som du kan se, er funktioner element af Agurk Valg lokaliserer funktionsfilen oprettet før. Et andet vigtigt element, kaldet lim, giver stier til trindefinitioner. Imidlertid, hvis testtilfælde og trindefinitioner er i samme pakke som i denne vejledning, kan dette element muligvis droppes.

4.3. Definitioner på skrivetrin

Når agurk analyserer trin, vil den søge efter metoder, der er kommenteret med agurkensøgeord for at finde de matchende trindefinitioner.

Et trindefinitions udtryk kan enten være et regulært udtryk eller et agurkeekspression. I denne vejledning bruger vi agurkeudtryk.

Det følgende er en metode, der fuldt ud matcher et agurkatrin. Metoden vil blive brugt til at sende data til en REST-webtjeneste:

@When ("brugere uploader data på et projekt") offentlige ugyldige brugereUploadDataOnAProject () kaster IOException {}

Og her er en metode, der matcher et agurkatrin og tager et argument fra teksten, som vil blive brugt til at få information fra en REST-webservice:

@When ("brugere ønsker at få oplysninger om {string} projektet") offentlige ugyldige brugereGetInformationOnAProject (String projectName) kaster IOException {}

Som du kan se, er usersGetInformationOnAProject metoden tager en Snor argument, som er projektnavnet. Dette argument erklæres af {snor} i kommentaren og herovre svarer den til Agurk i trinteksten.

Alternativt kunne vi bruge et regulært udtryk:

@When ("^ brugere ønsker at få oplysninger om '(. +)' Projekt $") offentlige ugyldige brugereGetInformationOnAProject (String projectName) kaster IOException {}

Bemærk, at ‘^' og ‘$' som angiver starten og slutningen af ​​regexen i overensstemmelse hermed. Der henviser til ‘(.+)' svarer til Snor parameter.

Vi giver arbejdskoden til begge ovenstående metoder i det næste afsnit.

4.4. Oprettelse og kørsel af test

Først begynder vi med en JSON-struktur for at illustrere de data, der uploades til serveren ved en POST-anmodning, og downloades til klienten ved hjælp af en GET. Denne struktur gemmes i jsonString felt og vist nedenfor:

{"testing-framework": "agurk", "understøttet sprog": ["Ruby", "Java", "Javascript", "PHP", "Python", "C ++"], "website": "agurk. io "}

For at demonstrere en REST API bruger vi en WireMock-server:

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

Derudover bruger vi Apache HttpClient API til at repræsentere den klient, der bruges til at oprette forbindelse til serveren:

CloseableHttpClient httpClient = HttpClients.createDefault ();

Lad os nu gå videre til at skrive testkode inden for trindefinitioner. Vi vil gøre dette for brugereUploadDataOnAProject metode først.

Serveren skal køre, før klienten opretter forbindelse til den:

wireMockServer.start ();

Brug af WireMock API til at stoppe REST-tjenesten:

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

Send nu en POST-anmodning med indholdet taget fra jsonString felt erklæret ovenfor til serveren:

HttpPost-anmodning = ny HttpPost ("// localhost:" + wireMockServer.port () + "/ create"); StringEntity-enhed = ny StringEntity (jsonString); request.addHeader ("indholdstype", "applikation / json"); request.setEntity (enhed); HttpResponse svar = httpClient.execute (anmodning);

Følgende kode bekræfter, at POST-anmodningen er modtaget og håndteret med succes:

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

Serveren skal stoppe efter brug:

wireMockServer.stop ();

Den anden metode, vi vil implementere heri er usersGetInformationOnAProject (String projectName). I lighed med den første test skal vi starte serveren og derefter stoppe REST-tjenesten:

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

Afsendelse af en GET-anmodning og modtagelse af svar:

HttpGet-anmodning = ny HttpGet ("// localhost:" + wireMockServer.port () + "/ projects /" + projectName.toLowerCase ()); request.addHeader ("accept", "application / json"); HttpResponse httpResponse = httpClient.execute (anmodning);

Vi konverterer httpResponse variabel til en Snor ved hjælp af en hjælper metode:

String responseString = convertResponseToString (httpResponse);

Her er implementeringen af ​​denne konverteringshjælpermetode:

private String convertResponseToString (HttpResponse respons) kaster IOException {InputStream responseStream = respons.getEntity (). getContent (); Scannerscanner = ny scanner (responseStream, "UTF-8"); String responseString = scanner.useDelimiter ("\ Z"). Næste (); scanner.close (); returnere responsString; }

Følgende bekræfter hele processen:

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

Endelig skal du stoppe serveren som beskrevet tidligere.

5. Kører funktioner parallelt

Agurk-JVM understøtter indbygget parallel testudførelse på tværs af flere tråde. Vi bruger JUnit sammen med Maven Failsafe-plugin til at udføre løberne. Alternativt kunne vi bruge Maven Surefire.

JUnit kører funktionsfilerne parallelt i stedet for scenarier, hvilket betyder alle scenarierne i en funktionsfil udføres af den samme tråd.

Lad os nu tilføje plugin-konfigurationen:

 maven-failsafe-plugin $ {maven-failsafe-plugin.version} CucumberIntegrationTest.java metoder 2 integration-test bekræft 

Noter det:

  • parallel: måske klasser, metodereller begge dele - i vores tilfælde, klasser får hver testklasse til at køre i en separat tråd
  • threadCount: angiver, hvor mange tråde der skal tildeles til denne udførelse

Det er alt, hvad vi skal gøre for at køre agurkfunktionerne parallelt.

6. Konklusion

I denne vejledning dækkede vi det grundlæggende i Agurk, og hvordan denne ramme bruger det Gherkin-domænespecifikke sprog til test af en REST API.

Som normalt er alle kodeeksempler vist i denne vejledning tilgængelige på GitHub.