Deel 4 - De testpiramide

En dan nu het praatje bij hét plaatje bij geautomatiseerd testen: de testautomatiseringspiramide van Mike Cohn.

De testautomatiseringspiramide

... oftewel de testpiramide is de vertaling van een belangrijk inzicht, namelijk dat testen zich afspeelt op verschillende niveaus. Van het technische niveau tot het eindgebruikersniveau. Op elk niveau kun je automatiseren en de manier van automatiseren is per niveau verschillend.

Het is dus van belang dat je je bewust bent van het niveau waarop je wilt automatiseren en de testpiramide helpt daarbij enorm. Zoals je weet: alle modellen zijn fout (of goed), maar sommige modellen zijn handig. De testpiramide is zo'n handig model want die helpt je bij het structureren van je geautomatiseerde testaanpak op alle niveau's, van techniek tot gebruikersbeleving.

OK, en nu heb ik een uitdaging. Want hoe leg je in een podcast een plaatje uit zónder het plaatje? Hier lukt het gelukkig wel:

Nou, dat gaat lukken, want deze piramide is heel simpel, Het zijn maar drie lagen en er zitten geen verborgen kamers in. De vloek van de farao's rust er ook al niet op, voor zover ik weet, maar verborgen schatten zitten er wél in, en die gaan we ontdekken.

Drie lagen dus, heel simpel:

Laag 1, de onderste laag: unit testen van softwarecomponenten door de bouwers

Laag 2, de middelste laag: testen van berichten via de Application Program Interface, de API

Laag 3, de bovenste laag: testen via de schermen, oftewel de GUI, de Grafische User Interface

Dus teken in je hoofd de piramide met 3 even dikke lagen, de onderste unit test laag het grootst, zoals een piramide betaamt. Daarop de API laag. En bovenaan de GUI laag, een smal puntje.

Op dat puntje wordt meestal ook nog een wolkje getekend. Dat wolkje staat voor handmatig testen. Opdat wij niet vergeten dat 100% geautomatiseerd testen vrijwel nooit mogelijk is.

Eerst even een belangrijk zijpaadje. De kans is groot dat je nu denkt, hé, dat doet best wel denken aan een ander plaatje, namelijk het V-model. Ook het V-model wordt (meestal) met drie lagen getekend, met unit testen onderaan, tussenin de systeemtesten en bovenaan acceptatietesten. De drie HOOFD-testsoorten dus, in het engels TEST LEVELS. Als je die V naast de testpiramide zet dan zie je mooi de overeenkomst tussen de drie lagen in beide modellen.

De match is niet 100% en de twee modellen hebben niet helemaal hetzelfde doel, maar de analogie is er zeer zeker!

OK, dan nu terug naar de boodschap van de testpiramide!

 

Waar ga je automatiseren: op Unit, API of GUI niveau?

De hoofdboodschap van de testpiramide is dat je geautomatiseerd testen op drie wezenlijk verschillende niveaus kunt oppakken. Daar moet je dus strategisch over nadenken: Op welk niveau ga ik beginnen en welk niveau laat ik nog even liggen? Wie gaat het doen, wie heeft de kennis, wie trekt de kar? Welke tools zijn voor dit niveau geschikt?

Een tweede boodschap is dat de onderkant het breedst moet zijn. Want investeren in het onderste niveau is het snelst en het goedkoopst. We zullen hierna zien waarom.

Bij het bepalen van je strategie hanteer je de drie bijbehorende principes:

1.     Test vroeg en veel op unit niveau

2.     Doe je integratietesten op API service niveau

3.     Automatiseer selectief via de GUI

In de rest van deze aflevering ga ik je deze drie principes grondig uitleggen.

OK, principe 1:

1. Test vroeg en veel op unit niveau

In state-of-the-art softwareontwikkeling beschouwen we stukken programmacode alleen 'done' als de bijbehorende geautomatiseerde unit testen worden meegeleverd.

Dit is de stevige basis van de testpiramide en investeren in dit onderste niveau levert niet alleen de gewenste directe feedback (hoe vroeger je een fout vindt hoe beter) maar heeft meer voordelen:

  • automatiseren aan de onderkant is relatief snel
  • unit testen zijn relatief stabiel en daarmee beter onderhoudbaar en goedkoper

Die focus op de onderkant past perfect bij 'test early, test first' aanpakken zoals Behaviour Driven Development (BDD[1]) en Test Driven Development (TDD) en Continuous Integration (CI). In de praktijk gaan die vrijwel altijd samen met geautomatiseerd testen.

Om losse units te kunnen testen heb je wel een test framework of test harnas nodig. Want met een kale brok code, oftewel een software unit, kun je niet zoveel. Want die heeft geen GUI of andere invoer- en uitvoervoorzieningen. En iets van dien aard heb je wel nodig om testinput aan te bieden en de uitkomsten te zien en te controleren. Je moet die brok code dus ergens in kunnen hangen om hem überhaupt te kunnen testen.

Wat je daarom nodig hebt is een test framework, ook wel testharnas of test bed genoemd. Deze voorzien in een testrunner, faciliteren de data setup en regelen de opbouw en afbraak van alle objecten, gebruikt binnen een test. Gedrag van nog niet beschikbare code wordt gesimuleerd (gemock't) en de respons van het testobject wordt afgevangen en geverifieerd. Dit is allemaal noodzakelijk omdat kale software units niet de context bieden die een complete applicatie wel biedt.

Softwareontwikkelaars weten hier over het algemeen wel raad mee en zijn in staat om met behulp van bestaande tools, infrastructuur en softwarebibliotheken een adequaat testharnas neer te zetten waarin ze hun unit testen soepel kunnen uitvoeren.

Tot zover principe 1, nu principe 2:

2. Doe je integratietesten op API-serviceniveau

Het is inmiddels een breed geaccepteerd inzicht dat testen van het berichtenverkeer op de service laag robuuster is dan testen via de GUI. GUI testen zijn laagdrempelig en zien er leuk uit, API testen lijken moeilijker en er valt niks te zien voor de gebruiker, maar zijn robuuster en sneller.

We testen dan het berichtenverkeer tússen de GUI en de applicatie of tussen applicaties onderling. Via de zogenaamde 'Application Program Interface' (afgekort EE PIE AAI of API). Op de machine-machine interface dus, en niet op de mens-machine interface. Bijvoorbeeld: 'doe mij de postcode van Orteliuslaan 1000, Utrecht', of 'bereken voor mij de maandpremie van een polis met die en die karakteristieken'. Of 'stuur me in XML-formaat de koersontwikkeling van de bitcoin van afgelopen week'. Meestal zijn dit gestandaardiseerde 'services' met standaard SOAP of REST protocollen en gestandaardiseerde XML of JSON dataformaten. Als deze services via het internet lopen noemen we ze 'webservices'.

Waarom zijn dit soort testen nou zo veel robuuster?

Dat zit zo: wijzigingen in de schermen zoals de plaatsing van velden of wijzigingen in de GUI-objecten hebben geen invloed op dit soort API testen: het berichtenverkeer verandert daar niet mee. Dat maakt de scripts stabieler, toekomstvaster en beter onderhoudbaar.

En waarom sneller? Dát zit zo: in tegenstelling tot testen via de GUI ben je niet afhankelijk van de snelheid van de server en van het laden van objecten en schermopbouw door de browser[2]. Selenium scripts bijvoorbeeld moeten daarvoor allerlei wachtmomenten hebben om te voorkomen dat de applicatie het niet kan bijbenen en je uit de pas gaat lopen waardoor je testen ten onrechte falen. Daardoor kan de geautomatiseerde testuitvoering gek genoeg heel tijdrovend worden. Op API niveau heb je daar geen last van.

Dat was principe 2, komen we bij principe 3:

3. Automatiseer selectief via de GUI en hou ruimte voor handmatig testen

Geautomatiseerd testen via de GUI lijkt ideaal, want het dekt het complete systeem, de hele zogenaamde 'technology stack'. Het staat bovendien het dichtst bij de eindgebruiker en benadert de werkelijkheid het beste. De eerder besproken 'knuffelfactor' is hier extra van toepassing; de aanblik van een test die je op de echte schermen voor je ogen automatisch ziet afspelen heeft iets magisch.

Dat zijn allemaal niet te onderschatten voordelen. Maar er zijn ook grote nadelen.

Ik noem er drie:

1.       Veel onderhoud - Zoals al gezegd: testen via de GUI is kwetsbaar en onderhoudsintensief, want aanpassingen in de GUI-schil leiden al snel tot falende scripts.

2.       Lastige foutanalyse - Het voordeel van dekking van de hele technology stack heeft ook een schaduwzijde: problemen kunnen op alle niveaus 'in de stack' ontstaan en dat maakt de analyse van gevonden fouten lastig en tijdrovend.

3.       Traagheid - Voor velen komt de traagheid als een verrassing. Toch is dat soms echt een probleem en ik heb zojuist al uitgelegd hoe dat zit. Trage scripts hinderen de snelle feedback en de hoeveelheid testen die je in één nacht of tussen twee releases kunt uitvoeren. Het beperkt dus je dekking en/of je releasefrequentie. Trage scripts zijn een argument om ze minder vaak te draaien, en dat is precies wat je niet wilt.

Nadat jarenlang de aandacht nogal eenzijdig naar automatisering via de GUI is gegaan hebben de ervaringen met deze nadelen geleid tot meer aandacht voor de onderkant van de piramide.

Volstrekt terecht, maar laat de conclusie niet zijn dat we helemaal van testen via de GUI af moeten. Het blijft natuurlijk een prima idee om ook de 'proof of the pudding' te automatiseren: de finale proefrit en ketenacceptatietest, black-box, end-to-end, met een ijzeren set van business scenario's. Als je dat combineert met een stukje vrijzwemmen en handmatig exploratief testen door de eindgebruikers dan heb je alle niveau's van een professioneel testtraject te pakken.

Zo, dat waren dus de boodschap en de lessen uit de testpiramide, een terecht populair - want superhandig - model bij het bepalen van je strategie voor geautomatiseerd testen.