TRIMS – Succesvol inzetten van Postman voor geautomatiseerd testen van API’s

Recent heb ik een blog gelezen over de factoren die testautomatisering een succes kunnen laten worden. Dit werd beschreven aan de hand van het ezelsbruggetje TRIMS, te zien in de volgende link: https://automationintesting.com/2019/08/trims-automation-in-testing-strategy.html 

TRIMS staat voor Targeted, Reliable, Informative, Maintainable en Speedy. Oftewel je testautomatisering moet:

  1. een doel dienen (anders dan enkel te automatiseren om te automatiseren).
  2. betrouwbaar zijn en zeker geen ‘false positives’ geven.
  3. informatief zijn en zo ook ondersteunen bij aanvullende ‘exploratory’ testen
  4. laagdrempelig onderhoudbaar zijn. Software is continue in beweging, het is een illusie dat testcode nooit meer aanpassingen behoeft.
  5. snel inzicht geven, maar bovenal snel aan te passen  zijn op basis van de onderhoudbaarheid.  Het kan zijn dat het het onderhoud en/of het verkrijgen van de testresultaten te lang gaat duren. Dit leidt dan geheid tot de ondergang van het succes van het gebruik van testautomatisering.

Op mijn visie van testautomatisering sluiten de bovengenoemde vijf speerpunten uitstekend aan. Al deze facetten zie ik terug in mijn huidige project. Hier werk ik hoofdzakelijk met Postman, (https://www.getpostman.com/downloads/ ). In deze blog neem ik je mee aan de hand van het gebruik van Postman, en hoe deze succes factoren toe te passen. Ondanks deze focus, herken je ongetwijfeld ook zaken die kunnen helpen in het verbeteren van het gebruik van een testtool anders dan Postman.

Targeted
De verleiding om direct hands-on aan de slag te gaan, is vaak groot. Toch is het verstandig eerst goed te bedenken welke scenario’s je wilt gaan testen. Werk hierbij altijd risico georiënteerd, en kijk naar welke functionaliteit er eerst wordt toegevoegd en/of gewijzigd. Weet je welke scenario’s je gaat testen? Bedenk dan vervolgens hoe je de validaties mogelijk kunt/wilt doen, zonder nog al te veel (javascript)code te schrijven. Hiermee help je jezelf om erachter te komen of je vergelijkbare assertions wilt uitvoeren. Wellicht zijn deze goed te voeden vanuit variabelen. Het onderhouden van geautomatiseerde testen is namelijk veel gemakkelijker wanneer je slechts met één stukje code werkt voor veel checks. Krijg je per scenario uitkomsten die slechts op bepaalde aspecten onderling afwijken, schrijf deze dan aan variabelen toe. Zorg per scenario dat deze gedefinieerd worden in je pre-request script. Roep deze variabelen vervolgens aan vanuit de testen op een hoger (sub)folder niveau. Op deze manier blijft de code onderhoudbaar. Meer hierover is te lezen bij Maintainable. Beschrijf scenario’s die op zijn minst de basisfunctionaliteit van de API-onder-test duidelijk verwoorden. Voeg, daar waar mogelijk is, details in beschrijvingen toe.  Voorbeeld: noem een request niet simpel en alleen “Happy flow”, maar “Add dog to pet store”. Zo weet je zeker dat je met een duidelijk doel test, en niet alleen ‘zinloze’ technische validaties uitvoert. Hoogstwaarschijnlijk worden deze toch al met unittesten getest. Anderzijds biedt dit een snel inzicht of daadwerkelijk alle ‘functionele’ requirements zijn gedekt.

postman

Reliable
Het is nogal een dooddoener om als tester te zeggen dat je zeker moet zijn dat wat je checkt ook daadwerkelijk checkt wat je denkt dat je checkt. Is deze logica nog te volgen? Kortweg gezegd, in Jip en Janneke taal, test je test! Het komt regelmatig voor dat testcode false positives geeft, wees hierop dus alert! Blijf altijd nadenken over de wijze van de assertions die je gebruikt voor het vaststellen van een ‘functioneel’ requirement. Wil je snel meters maken met het doen van juiste validaties? Dan beveel ik je van harte aan om de mogelijkheden van de Chai library te bestuderen en met name deze webpagina: https://www.chaijs.com/api/bdd/.  Deze pagina laat duidelijk zien wat er mogelijk is, maar ook welke checks over het algemeen betrouwbaarder zijn dan andere alternatieven. Daar aangegeven met ‘// Not recommended’ en ‘// Recommended’.  Hoewel Postman javascript in zijn algemeenheid accepteert, is het zeer aan te raden ook voor de leesbaarheid, en daarmee maintainability, gebruik te maken van deze BDD Chai library.

False positives
Voor het complete plaatje neem ik je graag mee in een praktisch voorbeeld omtrent false positives. Neem een situatie waarbij je denkt een fantastisch json schema en een mooie test te hebben.  Je hebt bijvoorbeeld een schema waarbij duidelijk is opgenomen dat bepaalde elementen van het type ‘string’, ‘number’ of ‘array’ moeten zijn. Waarbij dan blijkt het element ‘required’ te ontbreken. Zowel met de validatie op basis van tv4 (https://github.com/geraintluff/tv4) als ajv (https://github.com/epoberezkin/Ajv), doet jouw json validatie dan vrij weinig. Het kan zijn dat je een error response terug krijgt waarbij geen enkel element een overlappende naam heeft met de verwachte elementen. Dan zal de validatie zeggen dat de response aan het json schema voldoet. Dat kan natuurlijk niet de bedoeling zijn. Hieruit leren we dat wat je ook toevoegt aan je json schema, je altijd het element ‘required’ toe moet voegen.

Gezien de website van Postman (https://learning.getpostman.com/docs/postman/scripts/postman_sandbox/) zelf aangeeft dat de tv4 methode inmiddels ‘Deprecated’ is, geef ik hieronder een voorbeeld voor de ajv methode. Een kleine kanttekening: voor de leesbaarheid heb ik een super versimpeld json schema toegevoegd, (die om te experimenteren overigens prima werkt):

ajv methode

Aanvullende tip
Mochten de developers uit je team, om welke reden dan ook, geen kant en klare json schema’s kunnen/willen opleveren terwijl je software wel al json output oplevert, dan kun je de schema’s op diverse wijzen snel automatisch genereren en fijn tunen. Zo zijn er diverse online tools, maar kan ik mij voorstellen dat je, bijvoorbeeld vanwege security redenen, niet alle gevoelige informatie met het open internet wil delen. In dat geval kun je bijvoorbeeld een XMLSpy licensie overwegen, dit vind ik zelf een super fijne tool. Ondanks dat de naam anders doet vermoeden, kan deze software naast xml’s dus ook prima overweg met json’s. XMLSpy biedt ook super gebruiksvriendelijke mogelijkheden om bijvoorbeeld enumerations, max string length en nog veel meer toe te voegen aan de json schema’s, zonder dat je echt alle ins-en-outs hoeft te weten over hoe je een json schema schrijft.

Informative
Als een test faalt wil je snel in staat zijn om vast te stellen of het hier gaat om een bug of bijvoorbeeld een test fout. In alle gevallen wil je hierbij zo veel mogelijk informatie ter beschikking hebben om eenvoudig en snel een analyse uit te kunnen voeren, om een conclusies te kunnen trekken en eventueel zelfs al met een oplossing te kunnen komen.  Hiervoor zijn diverse mogelijke hulpmiddelen beschikbaar. Zo is er onder andere de mogelijkheid tot het wegschrijven van data naar de Postman console (shortkey CTRL + ALT + C). Daarnaast ook tot het extra feedback toevoegen aan de ‘standaard’ assertions, of bijvoorbeeld variabelen reeds te gebruiken in de beschrijving van de assertions. Hieronder licht ik er een aantal kort toe aan de hand van een voorbeeld: 

  1. console.log(“Your custom message”) or console.log(customObject)
  2. pm.expect(actualUsedTestDataFilePath, “test setup fails try to change your workdirectory”).to.match(expectedTestDataFilePath);
  3. pm.expect.fail(“The reason why this testcase fails is because your code did X, so probably condition Y or Z was not met while expected so”)
  4. pm.test(request.name + ” – variable name catched (” + actualNewAnimalName + “) and saved to environment variables for follow up test cases”, function () { … });

Console.log
Console.log heeft diverse mogelijkheden. De twee opties die ik het meest gebruik zijn het weergeven van een functioneel beschreven string, al dan niet opgebouwd uit tekst, en het weergeven van een waarde vanuit een variabel. Om een voorbeeld te geven: console.log(“there are found ” + actualResponseBody.length + ” animals with status \’available\'”); waarbij actualResponseBody uiteraard een eerder gedeclareerde variabel is, hier van het type ‘Array’. Het is echter ook mogelijk om arrays of volledige (json) objecten te retourneren, hieronder te zien in een voorbeeld prinstscreen. Dit kan erg zinvol zijn voor het debuggen en verkennen van test situaties.

Console.log

pm.expect.fail
Zijn er veel voorkomende foutsituaties, bijvoorbeeld in configuraties? Dan kan het soms zinvol zijn om binnen een if/else statement een scenario bewust te laten falen met een zeer specifieke foutmelding. Waar de console.log feedback alleen in de console wordt getoond, komt deze feedback direct terug in het testtab (bij het aftrappen van een individuele request) en/of in de testrunner (bij het runnen van vele testen tegelijk). Hieronder wordt wederom een voorbeeld weergegeven.

pm.expect.fail

pm.expect(x, “customized feedback”)
Ongeacht de vele schrijfwijzen van mogelijke assertions is het vaak mogelijk om, naast de standaard feedback, een optionele parameter/string mee te geven. In plaats van .expected(expectedValue).to.equal(actualValue); kan achter de eerste variabel het tweede element worden meegegeven met de letterlijke feedback die jij graag terug ziet als deze assertion zou falen. In plaats van “Expect ‘tekst X’ to equal ‘tekst Y’” krijg je nu bijvoorbeeld “Het geven Label A toont verkeerde uitkomst :  Expect ‘tekst X’ to equal ‘tekst Y’”. In de afbeelding hieronder is het resultaat te zien van beide schrijfwijzen:

pm.expect(x, “customized feedback”)

pm.test(request.name + ” – variable name catched (” + actualNewAnimalName + “)”
Soms kan het handig zijn tijdens het creëren van testen, en/of exploratory spelen met de scenario’s, om je feedback direct te krijgen zonder naar je console of test runner te hoeven kijken. Voor mij helpt het om bepaalde zaken (bijvoorbeeld unieke sleutels uit de database) reeds in de test naam terug te laten keren. Op deze manier kun je snel controleren of de juiste check wel op de juiste zaken wordt toegepast en of de scope nog altijd juist is.

pm.test(request.name + " - variable name catched (" + actualNewAnimalName + ")"

Maintainable
Al snel kwam ik er achter dat het verplaatsen van de testen voor individuele scenario’s naar een hoger (sub)folder level veel copy paste werk bespaart en de maintainabilty drastisch doet toenemen. Wanneer de code gewijzigd moest worden, was dit niet meer nodig op tientallen plekken tegelijk. Een andere eyeopener was het gebruik van if/else statements gecombineerd met “request.method” (zie voorbeeld hieronder). Hierdoor werd het mogelijk om binnen één (sub)folder flows te creëren waarbij je requests kon doen van de verschillende types, zoals “Post”, “Get”, “Put”. Dit zonder dat er onnodige testen falen, en/of aparte folder structuren aangemaakt hoefde te worden, (voor het testen en/of gebruiken van de verschillende type requests). Tevens werd het op deze manier dus mogelijk hele uitgangsposities te initialiseren, bijvoorbeeld door het gebruik van een “Post” of “Put” request om vervolgens met een “Get” de resultaten te checken. Dit alles mét behoud van onderhoudsvriendelijke checks op een hoger folder niveau.

Json validation example

Volgorde test levels
Wanneer je testen zowel op folder, of zoals te zien in bovenstaand voorbeeld op collection, level hebt gedefinieerd,  worden deze bij het runnen van je scenario in volgorde uitgevoerd. In onderstaande afbeelding zie je dat er op scenario level één test is geformuleerd. In het ‘Test Results’ tab zie je echter dat er twee testen zijn uitgevoerd.  De eerste van de twee was hierbij dus geformuleerd op collection level zoals hierboven aan gegeven. Afhankelijk van de complexiteit van je project is het aan jou om te beoordelen of je deze ‘generieke’ testen op (sub)folder level of collection level formuleert.

Collection

Reduceren van testen
Wil je het aantal testen voor onderhoudbaarheid verder reduceren? Dan kan het interessant zijn om gebruik te maken van variabelen die je in het pre-request Script tab per individueel scenario kunt initialiseren. Zo hebben wij in mijn huidige project per service een aantal ‘happy flow’ en ‘error flow’ situaties. De error flows geven vaak een gestandaardiseerde response terug met een vaste structuur en een duidelijke beschrijving waarom het request faalt. Deze structuur kunnen we op hoofdniveau checken. Het detailbericht zetten we als pre-request script in een variabele die door de testen in het hogere niveau aangeroepen wordt. Op deze manier kunnen we voor tientallen scenario’s, die elk hun eigen detailinformatie bevatten, toch uit de voeten met slechts één herbruikbare test.

Naamgeving variabelen
Voor maintainability en readability  heb ik de gewoonte om alle variabelen, die content van de daadwerkelijke response bevatten, te starten met de tekst ‘actual’. Tevens op vergelijkbare wijze de variabelen die informatie bevatten omtrent de verwachting te beginnen met ‘expected’. Dit heb ik zelf als een grote plus ervaren. Zeker als jij de test niet zelf hebt geschreven geeft dit een boost aan het begrijpen van de testen die je collega’s hebben geschreven.

Herleidbaarheid variabelen
Sinds kort geef ik ook in de benamingen aan waar bepaalde variabelen worden geïnitialiseerd. Hierbij maak ik onderscheid tussen collection variabelen en variabelen die gezet worden in een pre-request script op scenario level en/of (sub)folder level. Dit helpt om snel te bepalen of kopiëren (voor bijvoorbeeld nieuwe scenario’s) van requests en/of assertions verstandig is. Tevens geeft dit inzicht of het noodzakelijk is om bijvoorbeeld nieuwe variabelen te kiezen en/of andere waarden van de bestaande variabelen te overschrijven. Verder maak ik ook gebruik van variabelen die ik pas initialiseer tijdens de uitvoering van een test. Bijvoorbeeld voor het opvangen van bepaalde data uit de response die ik later in een ander request wil hergebruiken voor het testen van elkaar opvolgende flows

Wijzigingen
Het kan voorkomen dat je ondanks alle zorgvuldigheid omtrent maintainability toch veel vergelijkbare stukjes code/configuraties moeten wijzigen. Dit kan bijvoorbeeld omdat er in de basis van de software een grote (onvoorziene) wijziging heeft plaatsgevonden. Vergeet dan niet dat je ook nog steeds de mogelijkheid hebt om een export van je volledige collection te maken. Om deze dan vervolgens in een editor naar keuze te openen. Die je hierna met de CTRL+F methode, en/of andere handige zoek/replace acties (afhankelijk van je geselecteerde tool), snel en eenvoudig kunt uitvoeren. Zelf gebruik ik hier bijvoorbeeld UltraEdit voor. Dit kan bij grote wijzigingen vele malen efficiënter werken dan dit vanuit de Postman tool zelf te doen. Daarna kun je uiteraard even eenvoudig de collection file met wijzigingen weer importeren in de Postman GUI en daar naar hartenlust verder sleutelen.

Speedy
Wanneer nieuwe software is opgeleverd wil jij en/of je teamgenoten natuurlijk zo snel mogelijk feedback. Wees daarom bewust van de run time van je collectie met test scenario’s en assertions. Evalueer regelmatig of het mogelijk is om scenario’s, of op zijn minst assertions, te verwijderen. Duik voor de gein bijvoorbeeld eens wat dieper in de reeds uitgevoerde unit testen. Welke reeds zijn opgenomen in de code die gerund worden bij het builden van de software. Je zal dan bijvoorbeeld vast niet meer op systeem/integratie testniveau (en dus met Postman) willen checken of de applicatie daadwerkelijk een error geeft als de max field size is overschreden voor alle mogelijke input parameters. Je zal vast ook niet elke mogelijke waarde van enumeration list willen checken die allemaal al hoogstwaarschijnlijk zijn gecoverd in de veel snellere unit testen. 

Testdata versus performance
Als tip geef ik graag mee zo klein mogelijke testdata te gebruiken. In mijn huidige project is bijvoorbeeld vereist dat er een document (raw file) per call wordt meegestuurd. Gebruik hier bijvoorbeeld een plain txt file voor, zeker als de test scenario’s gericht zijn op de metadata en niet op de file zelf. Hiermee voorkom je trage testen/ lange doorlooptijden, door bijvoorbeeld het gebruik van relatief zware pdf’s of docx files. Wellicht wil je ook andere file extensies testen en weten of de systemen ook met grote files om kunnen gaan. Gebruik hier dan aparte scenario’s voor die zich hier specifiek op richten. In het geval van grote files wellicht zelfs een los project/collectie file. Een file die niet bij elke build of release noodzakelijkerwijs afgetrapt hoeft te worden. Terug komend op de eerste T van het TRIMS model: doe dit altijd ‘targeted’ en dus risico gericht. Bij bepaalde wijzigingen waar totaal geen risico te verwachten is op het kunnen verwerken van afwijkende extensies en/of grote files. Het is dus niet strikt noodzakelijk deze relatief zware testen uit te voeren.

Newman
Een gebruikersinterface is in het algemeen altijd trager dan alles wat op code niveau afgehandeld kan worden. Dit is ook zo ook voor Postman. Gebruik Postman voor het eenvoudig opzetten van je nieuwe regressie testen. Zo ook voor het debuggen van de software/test scenario’s en bovenal het exploratory verkennen van de werking van de software. Echter, zodra je regelmatig bestaande regressie testen wilt uitvoeren, start dan vooral met het gebruik van het veel snellere Newman. Newman is de comandline versie van Postman. Gebruik eenvoudig de export van de collection files vanuit Postman (en eventueel van de environment files indien van toepassing). Laat deze vervolgens aftrappen door Newman. Bij voorkeur uiteraard geïntegreerd in een build/deployement  pipeline. 

Neem gerust contact op
Over Speedy gesproken: wil je ook snel van start en wat experimenteren met Postman? Neem vooral contact op met mij via LinkedIn of via Newspark. Ik kan je dan eventueel de kant en klare json export van de hier behandelde voorbeelden toesturen. De hier getoonde voorbeelden maken gebruik van een vrij toegankelijke (Pet store) API en zijn dus werkend te gebruiken.
Tot slot: heb jij ook ervaring met Postman en of testautomatisering in zijn algemeenheid met betrekking tot de TRIMS begrippen? Dan ook kom ik graag met jou in contact (via LinkedIn). Ik ben benieuwd hoe jij omgaat met de uitdagingen zoals hier besproken, let me know!

André Koene
André Koene