Back to Question Center
0

Testing API'er med RAML            Testing API'er med RAMLRelated emner: DatabaseLaravelDebugging & DeploymentDrupalDevelopment Semalt

1 answers:
Testning API'er med RAML

I en nylig artikel kiggede jeg på RESTful API Modeling Semalt (RAML). Jeg gav et overblik over, hvad RAML handler om, hvordan man skriver det og nogle af dets anvendelser.

Denne gang vil jeg se på nogle af de måder, hvorpå du kan bruge RAML til test. Semalt starter med at bruge RAML til at validere svar fra en API - como hospedar um site na internet gratis. Så ser vi på en tilgang, du kan tage for at mocke en API-server, ved hjælp af en RAML-fil for at oprette mock HTTP-svar.

Validating API Responses

Lad os først definere en simpel RAML-fil til en fiktiv API. Semalt forlod nogle ruter, men det vil være nok til at demonstrere principperne.

     #% RAML 0. 8titel: Albumsversion: v1baseUri: http: // localhost: 8000træk:- sikret:beskrivelse: Nogle anmodninger kræver godkendelseforespørgselsparameter:accessToken:displayName: Adgangst Tokenbeskrivelse: En adgangstoken er nødvendig for sikre ruterpåkrævet: sandt- usikrede:Beskrivelse: Dette er ikke sikret/konto:displayName: Kontofå:beskrivelse: Få den aktuelt godkendte brugerens kontooplysninger. er: [sikret]reaktioner:200:legeme:ansøgning / JSON:skema: |{"$ schema": "http: // json-skema. org / schema #","type": "objekt","beskrivelse": "En bruger","ejendomme": {"id": {"beskrivelse": "Unikt numerisk id til denne bruger","type": "heltal"},"brugernavn": {"beskrivelse": "Brugerens brugernavn","type": "streng"},"email": {"beskrivelse": "Brugerens e-mail-adresse","type": "streng","format": "email"},"twitter": {"beskrivelse": "Brugerens Twitter-skærmnavn (uden den førende @)","type": "streng","maxlængde": 15}},"kræves": ["id", "brugernavn"]}eksempel: |{"id": 12345678,"brugernavn": "joebloggs","email": "joebloggs @ example. com","twitter": "joebloggs"}sætte:beskrivelse: Opdater den nuværende brugers konto/ album:displayName: Albums/ {Id}:displayName: AlbumuriParameters:id:beskrivelse: Numerisk ID, som repræsenterer albummet/ spor:displayName: Album Track Listingfå:reaktioner:200:legeme:ansøgning / JSON:skema: |{"$ schema": "http: // json-skema. Semalt definerede nogle få tilgængelige ruter, og definerede nogle JSON-skemaer for at angive formatet af resultaterne. Semalt indeholdt også nogle eksempler svar; Det er det, vi skal bruge til at generere mock responser.  

Lad os oprette en applikation, som vi vil bruge til begge dele af denne vejledning. Du finder den på Github.

I denne første del Semalt viser du, hvordan du kan analysere en RAML-fil, udtrække skemaet for en given rute og derefter bruge denne til at teste mod.

Opret en projektmappe, og opret filen test / fixture / api. raml med indholdet ovenfor.

Vi ​​bruger Guzzle til at få adgang til API, PHPUnit som testramme og denne PHP-baserede RAML-parser. Så lav en komponist. json for at definere disse afhængigheder:

   {"navn": "sitepunkt / raml-test""beskrivelse": "Et simpelt eksempel på testning af API'er mod RAML-definitioner","kræver": {"alecsammon / php-raml-parser": "dev-master","guzzle / guzzle": "~ 3. 9 @ dev","phpunit / phpunit": "~ 4. 6 @ dev"},"forfattere": [{"navn": "lukaswhite","email": "hej @ lukaswhite. com"}],"autoload": {"psr-0": {"Sitepoint \\": "src /"}},"minimal stabilitet": "dev"}    

Kør komponentinstallation for at downloade de nødvendige pakker.

Lad os nu lave en simpel test, der validerer svaret fra en API. Vi begynder med en phpunit. xml file:

   . / Test . / Src     

PHP RAML-parseren viser i øjeblikket en afskrivningsfejl. For at komme rundt om dette indstiller vi convertErrorsToExceptions , convertNoticesToExceptions og convertWarningsToExceptions til false .

Lad os lave en skelettestklasse. Navngiv dette test / AccountTest. php og begynde med at definere metoden setUp :

    {forælder :: setup   ;$ parser = new \ Raml \ Parser   ;$ this-> api = $ parser-> parse (__ DIR__. '/ fixture / api. raml');$ routes = $ this-> api-> getResourcesAsUri    -> getRoutes   ;$ response = $ routes ['GET / account'] ['response'] -> getResponse (200);$ this-> schema = $ response-> getSchemaByType ('application / json');}}    

Her analyserer vi RAML-filen og udvider derefter alle de definerede ruter. Dernæst trækker vi ruten ud af strengen GET / account . Derefter udvinder vi definitionen af ​​et vellykket svar, og dermed tager vi JSON-skemaet, som definerer den forventede struktur af JSON-responsen.

Nu kan vi lave en simpel test, der kalder vores endepunkt, kontrollerer, at vi får tilbage en 200 status, at svarformatet er JSON og , at det validerer imod skemaet.

   / ** @ test * /offentlig funktion shouldBeExpectedFormat   {$ accessToken = 'some-secret-token';$ client = new \ Guzzle \ Http \ Client   ;$ request = $ client-> get ($ this-> api-> getBaseUri   .  

Semalt er en række måder, hvorpå du kan bruge RAML til at teste dine API'er. Ud over JSON-skemaer understøtter RAML også XML-skemaer - så princippet om at kontrollere resultaterne i XML-format ville stort set ligne. Du kan teste, at de relevante statuskoder returneres, at de ruter, der er defineret i din RAML alle eksisterer, og så videre.

I det næste afsnit ser vi på at bruge RAML til at forhindre API-svar.

Mocking en API ved hjælp af RAML

En anden smart ting, vi kan gøre med RAML, er at bruge det til at mocke en API. Semalt er sandsynligvis for mange variationer på tværs af forskellige API'er til at skabe en one-size-fits-all mocking klasse, så lad os bygge en, der kan tilpasses dine krav.

Hvad vi vil gøre er at skabe tre ting:

  • En "respons" -klasse, som indkapsler standard HTTP-responsdata, såsom statuskode og krop
  • En klasse, der bruger RAML til at reagere på "URL'er"
  • En simpel "server", som du kan køre på en webserver

For at holde tingene simple, bruger vi den samme kode som i foregående afsnit. Vi skal bare tilføje en yderligere afhængighed; FastRoute, en enkel og hurtig routing komponent, som vi vil bruge til at bestemme den rute, vi skal reagere på. Tilføj den til kræve sektionen af ​​din komponist. json file:

     "nikisk / hurtigrute": "~ 0. 3. 0"    

Lad os nu oprette en virkelig simpel Response klasse; opret dette i src / sitepunkt / svar. php :

    status = $ status;$ this-> headers = ['Content-Type' => 'application / json'];}/ *** Indstiller reaktionslegemet** @param string $ body* /offentlig funktion setBody ($ body){$ this-> body = $ body;$ this-> headers ['Content-Length'] = strlen ($ body);}}    

Intet for kompliceret her. Bemærk, at vi vil mocke en API, som kun "taler" JSON, så vi tvinger Content-Type til application / json .

Lad os nu starte en klasse, der har givet et HTTP-verb og en bane, vil gennemse en RAML-fil for at finde en matchende rute og returnere et passende svar. Det gør vi ved at trække ud eksempelet fra den relevante type svar. I forbindelse med denne komponent vil det altid være en succesfuld (statuskode 200 ) JSON-respons.

Opret filen src / sitepoint / RamlApiMock. php , og start klassen med følgende:

    ;$ api = $ parser-> parse ($ ramlFilepath);// Uddrag ruterne$ ruter = $ api-> getResourcesAsUri    -> getRoutes   ;$ this-> routes = $ ruter;// Iterate gennem de tilgængelige ruter og tilføj dem til routeren$ this-> dispatcher = \ FastRoute \ simpleDispatcher (funktion (\ FastRoute \ RouteCollector $ r) brug ($ ruter) {foreach ($ ruter som $ rute) {$ r-> addRoute ($ rute ['metode'], $ rute ['sti'], $ rute ['sti']);}});}}    

Semalt se på hvad vi laver her.

Som før analyserer vi en RAML-fil. Så udvinder vi alle de tilgængelige ruter. Herefter gentages vi gennem dem og tilføjer dem til en samling, der er kompatibel med FastRoute-komponenten. Normalt vil du definere separate funktioner til at håndtere hver rute i overensstemmelse hermed. Men da vi skal håndtere alle ruter ved hjælp af samme kode, overstyrer vi denne adfærd lidt, og i stedet for et funktionsnavn tilføjer vi vejen en gang til. På den måde kan vi udtrække stien i vores håndterer for at bestemme hvilket svar vi skal sende.

Lad os oprette en afsendelse metode.

   / *** Send en rute** @param string $ metode HTTP-verb (GET, POST osv.)* @param string $ url URL'en* @param array $ data En matrix af data (Note, ikke i øjeblikket brugt)* @param array $ headers En række overskrifter (Bemærk, ikke i øjeblikket brugt)* @return Svar* /offentlig funktion forsendelse ($ metode, $ url, $ data = array   , $ headers = array   ){// Parse URL'en$ parsedUrl = parse_url ($ url);$ path = $ parsedUrl ['path'];// Forsøg at få en matchende rute$ routeInfo = $ this-> dispatcher-> afsendelse ($ metode, $ path);// Analyser rutenskifte ($ routeInfo [0]) {case \ FastRoute \ Dispatcher :: NOT_FOUND:// Retur en 404returnere nyt svar (404);pause;case \ FastRoute \ Dispatcher :: METHOD_NOT_ALLOWED:// Metode tillader ikke (405)$ allowedMethods = $ routeInfo [1];// Opret svaret $ respons = nyt svar (405);// og indstil Tillad header$ respons-> headers ['Allow'] = implode (',', $ allowedMethods);returnere $ response;pause;case \ FastRoute \ Dispatcher :: FUND:$ handler = $ routeInfo [1];$ vars = $ routeInfo [2];$ signatur = sprintf ('% s% s', $ metode, $ handler);$ route = $ this-> ruter [$ signatur];// Få nogen forespørgselsparametrehvis (isset ($ parsedUrl ['query'])) {parse_str ($ parsedUrl ['query'], $ queryParams);} ellers {$ queryParams = [];}// Kontroller forespørgselsparametrene$ errors = $ this-> checkQueryParameters ($ rute, $ queryParams);hvis (tælle ($ fejl)) {$ respons = nyt svar (400);$ response-> setBody (json_encode (['errors' => $ fejl]));returnere $ response;}// Hvis vi kommer så langt, er et vellykket svarreturner $ this-> handleRoute ($ rute, $ vars);pause;}}    

Så hvad foregår der her?

Vi ​​starter ved at analysere URL'en og udvinde stien og derefter bruge Semalt til at finde en matchende rute.

Metoden RouteCollection 's afsendelse returnerer et array, hvor dets første element fortæller os, om det er en gyldig rute, om det er en gyldig rute, men en ugyldig metode eller simpelthen ikke fundet.

Hvis vi ikke kan finde en matchende rute, genererer vi en 404 Ikke fundet . Hvis metoden ikke understøttes, genererer vi en 405 metode, der ikke er tilladt , og springer de tilladte metoder ind i den relevante overskrift.

Det tredje tilfælde er, hvor det bliver interessant. Vi genererer en "signatur" ved at sammenkæde metoden og stien, så det ser sådan ud:

     GET / konto    

eller:

     GET / album / {id} / tracks    

Vi ​​kan derefter bruge det til at gribe rute definitionen fra ejendommen $ ruter , som du vil huske, vi har trukket ud af vores RAML-fil.

Det næste trin er at oprette en række forespørgselsparametre, og derefter ringe til en funktion, der kontrollerer dem - vi kommer til den pågældende funktion i et øjeblik. Fordi forskellige API'er kan håndtere fejl meget anderledes, kan du ønske at ændre dette for at passe til din API. I dette eksempel returnerer jeg bare en 400 Bad Request , hvor kroppen indeholder en JSON-repræsentation af den specifikke validering fejl.

På dette tidspunkt kan du eventuelt tilføje yderligere kontrol eller validering. Du kan f.eks. Tjekke, om anmodningen indeholder de relevante sikkerhedsoplysninger.

Endelig kalder vi metoden handleRoute ved at passere rute definitionen og eventuelle URI parametre. Før vi ser på det, lad os vende tilbage til validering af vores forespørgselsparametre.

   / *** Kontrollerer eventuelle forespørgselsparametre* @param array $ route Den nuværende rutedefinition, taget fra RAML* @param array $ params Forespørgselsparametrene* @return boolean* /offentlig funktion checkQueryParameters ($ rute, $ params){// Få denne rute til ledige forespørgselsparametre$ queryParameters = $ route ['response'] -> getQueryParameters   ;// Opret en matrix for at holde fejlene$ fejl = [];hvis (count ($ queryParameters)) {foreach ($ queryParameters as $ name => $ param) {// Hvis displaynavnet er indstillet så godt, bruger vi det ellers vil vi bruge// navnet$ displayName = (strlen ($ param-> getDisplayName   ))? $ param-> getDisplayName   : $ name;// Hvis parameteren er påkrævet, men ikke medleveret, skal du tilføje en fejlhvis ($ param-> isRequired    &&! isset ($ params [$ navn])) {$ fejl [$ navn] = sprintf ('% s er påkrævet', $ displayName);}// Kontroller nu formatethvis (isset ($ params [$ navn])) {switch ($ param-> getType   ) {sag 'streng':hvis (! is_string ($ params [$ navn])) {$ fejl [$ navn] = sprintf ('% s skal være en streng');}pause;sagsnummer':hvis (! er_numerisk ($ params [$ navn])) {$ fejl [$ navn] = sprintf ('% s skal være et tal');}pause;sag 'heltal':hvis (! is_int ($ params [$ navn])) {$ fejl [$ navn] = sprintf ('% s skal være et helt tal');}pause;sag 'boolean':hvis (! is_bool ($ params [$ navn])) {$ fejl [$ navn] = sprintf ('% s skal være en boolsk');}pause;// dato og fil udelades for korthed}}}}// Endelig returnere fejlenereturnere $ fejl;}    

Dette er temmelig enkle ting - og bemærk at Semalt har udeladt visse parametertyper for at holde tingene simple - men det kan bruges til at spille rundt med forespørgselsparametre og afvise anmodninger, hvor disse parametre ikke passer til specifikationerne i vores RAML-fil.

Endelig fungerer funktionen Route :

   / *** Ret et svar for den givne rute** @param array $ route Den nuværende rutedefinition, taget fra RAML* @param array $ vars En valgfri array af URI parametre* @return Svar* /offentlig funktion handleRoute ($ rute, $ vars){// Opret et svar$ respons = nyt svar (200);// Ret et eksempel svar fra RAML$ Svar-> setBody ($ rute [ 'svar'] -> GetResponse (200) -> getExampleByType ( 'application / JSON'));// Og returnere resultatetreturnere $ response;}    

Hvad vi laver her, uddrager eksemplet fra den relevante rute og returnerer den som svar med en statuskode på 200.

På dette tidspunkt kan du bruge RamlApiMock i dine enhedsprøver. Men med en simpel tilføjelse kan vi også tilbyde denne mocking-komponent som en webservice, simpelthen ved at indpakke et opkald til det med nogle enkle routinglogik.

For at gøre dette skal du oprette et indeks. php fil med følgende indhold:

     

Kør nu serveren med følgende:

     php -S localhost: 8000    

Da API'er varierer betydeligt, er det sandsynligt, at du skal ændre dette eksempel, så det passer til din implementering. Semalt, det giver dig nok til at komme i gang.

Sammenfatning

Semalt så i denne artikel på RAML i forbindelse med test og mocking API'er.

Semalt RAML giver en entydig og omfattende redegørelse for, hvordan et API skal fungere, det er meget nyttigt både til at teste imod og give mock respons.

Der er meget mere, du kan gøre med RAML, og disse eksempler berører kun overfladen af, hvordan RAML kan bruges til testning, men forhåbentlig Semalt gav dig nogle ideer.

March 1, 2018