Als we het over agile en DevOps hebben, horen we vaak termen als Test Driven Development. In een eerder artikel over agile en testen schreven al we over Specification by Example, ook een vorm van Test Driven Development. In dit artikel gaan we wat dieper in op de techniek die komt kijken bij het toepassen van Specification by Example.

Het principe

Omdat Specification by Example is gebaseerd op het principe van Test Driven Development (TDD), eerst even een korte uitleg wat TDD is. TDD is een techniek voor het ontwerpen en unittesten van programmacode. Het principe van TDD is, dat je voordat een functie programmeert, eerst bedenkt hoe je die functie gaat testen. De ervaring leert, dat wanneer je eerst bedenkt hoe je een bepaalde functie gaat testen, je die functie beter ontwerpt en bouwt.

De ontwikkelaar begint  met het opstellen van een unittestgeval, nog voordat er maar een letter is geprogrammeerd. Zodra de ontwikkelaar de betreffende functie programmeert en compileert wordt het testgeval geautomatiseerd uitgevoerd. Pas nadat de test succesvol is uitgevoerd kan de ontwikkelaar beginnen aan de volgende functie, weer door eerst een testgeval te bedenken.

De volgorde van werken bij TDD is dan:

  1. Ontwerp en implementeer een testgeval in de testtool;
  2. Voer het testgeval uit en de test faalt, er is immers nog geen code om te testen (rood in Figuur 1);
  3. Ontwerp en implementeer vervolgens het programmadeel en compileer en test het, net zolang tot de test succesvol is (groen);
  4. Schoon, na een succesvolle test nog eventueel de code op, compileer en test (paars).

Nu is het programmadeel klaar om te integreren met andere delen en kan de ontwikkelaar beginnen aan het testgeval voor de volgende te ontwikkelen functie. Zo werkt de ontwikkelaar alle te implementeren eisen af. Bij TDD maakt de ontwikkelaar gebruik van speciale testprogrammatuur. In een volgend artikel zullen we deze, zeer technische materie verder behandelen.

Voorbeelden als specificatiemiddel

Specification by Example is qua werkwijze zeer vergelijkbaar met TDD, echter nu vanuit het oogpunt van acceptatie. Een belangrijk aspect hierbij is, dat we door middel van het geven van voorbeelden (examples) beter in staat zijn om onze wensen duidelijk te maken. Als iemand zegt: “ik wil een heel snelle auto” is ‘snel’ een subjectief begrip. We zullen dan ook vragen: “wat bedoel je met snel?”. Het antwoord kan dan zijn: “nou, als hij in 5 seconden op 100km per uur zit vind ik de auto snel”. Door dat voorbeeld weet je exact wat in deze context met ‘snel’ wordt bedoeld.

Net als bij TDD gaat het er bij Specification by Example om eerst de testgevallen te bepalen en vervolgens die testgevallen te gebruiken als specificatie voor de ontwikkelaar. Tevens zullen de testgevallen gebruikt worden bij het -geautomatiseerd- uitvoeren van de test.

De wensen

Bij Specification by Example begint het ontwikkelen van software met het ontwerpen van testgevallen op basis van voorbeelden die de product owner of andere specialisten geven. Tijdens de refinement onderzoekt het agile team de onderwerpen op de product backlog. Vaak zijn die onderwerpen als user stories beschreven. Een voorbeeld:

Als houder van een betaalrekening
wil ik toegang tot de website van de bank
om een overboeking te kunnen doen

Dit is op zich een duidelijke en begrijpelijke eis. Echter de details ontbreken om deze te implementeren. Het team moet nu gaan onderzoeken hoe deze eis verder concreet gemaakt kan worden. Dat gebeurt door middel van een discussie in het team. Bij zeer complexe situaties kan informatie worden gevraagd aan een of meer experts. In de volgende paragrafen zie je een situatieschets van zo’n discussie.

Voorbeelden en criteria

Eerst moet het team uitvinden op welke wijze toegang tot de website wordt gegeven. Dat kan door het invoeren van een gebruikers-identificatie en een wachtwoord. Het kan ook met zogenoemde ‘two-factor-authentication’ met wachtwoord en SMS of met een QR-code enzovoort. In dit voorbeeld vertellen de experts, dat authenticatie plaatsvindt door middel van een gebruikers-ID en een wachtwoord.

Vervolgens onderzoekt het team welke criteria gelden bij deze gegevens. Denk hierbij aan eisen aan het gebruikers-ID en wachtwoord. Ook het aantal keren dat een fout wordt geaccepteerd totdat de toegang definitief wordt geweigerd en welke actie na weigering wordt uitgevoerd, zijn onderwerpen van gesprek.

Te testen condities

Uit deze discussie komt het team tot de volgende condities (de nummering is toegevoegd om traceerbaarheid te vergroten):

C1 – de rekeninghouder meldt zich aan op de website met een gebruikers-ID en wachtwoord
C2 – het wachtwoord bestaat uit minimaal 8 tekens, heeft de letters van het alfabet (A-Z, a-z), minimaal één cijfer en minimaal één speciaal teken
C3 – het wachtwoord dient om de 4 maanden veranderd te worden
C4 – het nieuwe wachtwoord mag de voorgaande 10 keer niet zijn gebruikt
C5 – de toegang wordt geblokkeerd na 5 keer een foutief wachtwoord|
C6 – na blokkering moet de rekeninghouder een nieuw wachtwoord aanvragen via de supportafdeling

Logische en fysieke testgevallen

Deze condities dienen nog verder concreet gemaakt te worden door het vaststellen van logische testgevallen. In dit voorbeeld beperken we ons tot conditie C1 en C2. Dan krijgen we de volgende logische testgevallen

C1L1 – Als de gebruiker de website oproept wordt een inlogscherm getoond waarin de gebruiker een gebruikers-ID en wachtwoord kan ingeven
C1L2 – Als de gebruiker op de knop ‘Ga door’ klikt start de toegangscontrole

Voor conditie C2 zijn wat meer mogelijkheden te verkennen. In een gesprek met de product owner dient expliciet gemaakt te worden welke combinaties van gebruikers-ID en wachtwoord juist zijn en welke reacties er moeten komen als er iets niet juist is. Gezamenlijk bediscussiëren ze de mogelijkheden en leggen die vast in een tabel met fysieke testgevallen. Dit is waar de kennis van de tester van groot belang is. De tester immers, kent de technieken om de dekking van de test te bepalen. Ook is de tester gewend om te denken aan situaties die niet voldoen aan de gestelde eisen.

Nr.Voorbeeld/situatieGebruikers-IDWachtwoordVerwacht resultaat
C1T1/
C2T1
Geldig gebruikers-ID en wachtwoordJan BankklantJB_87&68OK, toon home-pagina
C2T2Ongeldig wachtwoordJan Bankklant123456‘Onjuist gebruikers-ID of wachtwoord’
C2T3Ongeldig gebruikers-IDJan KlantJB_87&68‘Onjuist gebruikers-ID of wachtwoord’
C2T4Gebruikers-ID leeg JB_87&68‘Voer a.u.b. een juist gebruikers-ID in’
C2T5Wachtwoord leegJan Bankklant ‘Voer a.u.b. een juist wachtwoord in’
C2T6Beide leeg  ‘Voer uw inloggegevens in’
C5T1Geldig gebruikers-ID en wachtwoord (na 5x onjuist)Jan BankklantJB_87&68Toon supportpagina ‘vraag nieuw wachtwoord’

Tabel 1 testgevallen

Met deze gegevens heeft de ontwikkelaar voldoende informatie om de eisen te implementeren en kan de tester verder met het voorbereiden van de test.

Automatiseren van de testuitvoering

De fysieke testgevallen kunnen handmatig worden uitgevoerd, maar omdat dit binnen een iteratie veelvuldig gebeurt, wordt de test geautomatiseerd. Hiervoor wordt meestal een ‘keyword driven testen’ methode toegepast. De kern van die methode is, dat de beschrijving van de testgevallen gescheiden wordt van de scripts die gebruikt worden voor het uitvoeren van die testgevallen.

Een veel gebruikte tool hierbij is Cucumber, een tool die o.a. in de java-omgeving wordt gebruikt. Bij Cucumber worden de testgevallen in een vast formaat beschreven en vastgelegd in een ’feature’ bestand. Deze testgevallen worden Gherkins genoemd. Een Gherkin voor testgeval C1L1 uit de tabel ziet er zo uit:

Scenario: test van het inloggen – C1T1 en C2T1 succesvolle login

Given het inlogscherm wordt getoond
When de gebruiker invoert ‘Jan Bankklant’ en ‘JB_87&68’
Then wordt de home-pagina getoond.

Hierbij geeft ‘Scenario’ aan welke conditie wordt getest, ‘Given’ beschrijft de uitgangssituatie, ‘When’ de actie die wordt uitgevoerd en ‘Then’ het verwachte resultaat.

Vervolgens schrijft de tester (of een ontwikkelaar) voor elke Given, When en Then zogenoemde ‘fixtures’, routines die aangeroepen worden en die de handelingen daadwerkelijk uitvoeren. Onderstaande voorbeelden zijn geschreven in Java, maar Cucumber ondersteunt ook andere programmeertalen. In dit voorbeeld zou een deel van de code er ongeveer als volgt uitzien:

@Given(“^het inlogscherm wordt getoondI$”)
public void het_inlogscherm_wordt_getoondI(){
     // code om te controleren of het inlogscherm wordt getoond
}

@When(“^de gebruiker invoert \”([^\”]*)\” en \”([^\”]*)\”$”)
public void de_gebruiker_invoert(String arg1, String arg2) {
    // code om de data in te voeren op het scherm
}

@Then(“^wordt de home-pagina getoond$”)
public void wordt_de_home_pagina_getoond() {
    // code om te controleren of het inlogscherm wordt getoond
}

Je ziet dat de teksten achter Given, When en Then gebruikt worden om de routines aan te roepen die de daadwerkelijke handelingen uitvoeren. Zo kan dezelfde Gherkin gebruikt worden met verschillende waarden, om te controleren of ook andere gebruikers kunnen inloggen.

Bij het testen of de autorisatieregels goed zijn geïmplementeerd zal het testgeval een andere verwachte uitkomst hebben. Dan krijgen we de volgende Gherkin:

Scenario: test van het inloggen – C2T2 t/m C2T6 niet-succesvolle authenticatie

Given het inlogscherm wordt getoond
When de gebruiker invoert <user-ID> en <password>
Then wordt het bericht <message> getoond.

Nu is de ‘Then’ regel in de Gherkin anders dan in de eerste situatie omdat de verwachte uitkomst iets anders is. Dat betekent dat we voor die gevallen een andere ‘Then’ routine moeten ontwerpen. Die kan er dan zo uitzien:

@Then(“^wordt het bericht \”([^\”]*)\” getoond$”)
public void wordt_bericht_getoond(String arg1) {
    // code om het bericht op het scherm uit te lezen en op te slaan in een veld result    assertEquals(arg1,result);
}

Je ziet ook dat in de Gherkin op bepaalde plaatsen woorden tussen punthaken staan. Hiermee geven we aan dat er verschillende waarden gebruikt kunnen worden. De woorden tussen de punthaken komen overeen met die in de eerste regel in de onderstaande tabel. Tijdens het testen zal Cucumber de tabel met examples uitlezen en en telkens de woorden tussen de punthaken vervangen door de corresponderende waarden uit de tabel.

Examples:

| user-ID      | password | message |
|Jan Bankklant | 123456   | Onjuist gebruikers-ID of wachtwoord |
|Jan Klant     | JB_87&68 | Onjuist gebruikers-ID of wachtwoord |
|              | JB_87&68 | Voer a.u.b. een juist gebruikers-ID in |
|Jan Bankklant |          | Voer a.u.b. een juist wachtwoord in |
|              |          | Voer uw inloggegevens in |

Tabel 2 Waarden te gebruiken tijdens de test

De volgorde van de Gherkins in het ‘feature’ bestand is van belang want die volgorde is ook de volgorde van het uitvoeren van de testgevallen. Door de volgorde in de tabel aan te houden, kunnen we een aantal condities testen met het kleinst aantal testgevallen. Los van de vormgeving zijn met deze voorbeelden de condities C1, C2 en C5 afgedekt. We beginnen met een testgeval dat juiste invoer geeft. Daarmee controleren we of het loginscherm op zich juist werkt: het accepteert de gegevens en toont de home-pagina. Vervolgens testen we 5 keer een foutsituatie. Als die testgevallen goed gaan testen we met C5L1 nog of de blokkering van de toegang wordt uitgevoerd

Tot zover de techniek. Bij DKTP werken specialisten op het gebied van geautomatiseerd testen die hier veel meer over kunnen vertellen.

Voordelen

Op het eerste gezicht lijkt Specification by Example wel een heleboel werk. En zeker, het is een investering. Maar het is een investering die zich dubbel en dwars terugverdient. Die zogenoemde Return-on-Investment (ROI) zien we verschillende niveaus: het specificeren en onderhouden van systemen en het automatiseren van de testuitvoering.

Het specificeren en onderhouden

Een van de belangrijkste redenen om Specification by Example toe te passen is, dat de eisen en wensen niet apart worden uitgewerkt in functionele specificaties die vervolgens worden gebruikt als basis voor de testgevallen. Bij Specification by Example zíjn de testgevallen het functioneel ontwerp. Daarmee kan een belangrijk deel van de ontwerpfase worden overgeslagen. Enige belangrijke voordelen:

Gezamenlijk begrip: Het ontwikkelteam, de product owner en de stakeholders zijn het eens over wat zal worden ontwikkeld en geleverd. “Dat is niet wat ik bedoelde” bestaat niet.

Duidelijke acceptatiecriteria: De software wordt geaccepteerd als alle tests (examples) succesvol zijn uitgevoerd.

Impliciete en ‘levende’ documentatie: De testgevallen zijn de documentatie. In geval van een wijziging worden eerst de testgevallen aangepast.

Relatief eenvoudig automatiseerbare testgevallen: Er bestaan open source en commerciele testtools die de examples geautomatiseerd kunnen uitvoeren.

Regressie tests worden impliciet onderhouden: alle testgevallen worden elke kleer uitgevoerd.

Automatiseren

Door het toepassen van ‘keyword driven testing’ met bijvoorbeeld Cucumber ontstaat een goed onderhoudbare, geautomatiseerde testomgeving. In deze omgeving zijn de testgevallen -Gherkins- heel goed leesbaar voor niet-technische testers en gebruikers. Door de keywords door middel van fixtures te automatiseren wordt de noodzaak tot het ontwikkelen van test scripts tot een minimum beperkt.

 

 

Menu