Richard | 24 maart 2021
Een test double kun je vergelijken met een stuntdubbel, vandaar ook de naam. Een stuntdubbel vervangt een acteur voor specifieke stunts zonder dat dit opvalt. Dit is ook zo met test doubles; al hebben we het natuurlijk niet over stunts, maar over een object in je code.
Bij geautomatiseerd testen wordt vaak gebruikgemaakt van zogeheten unittests. Dit zijn tests die specifiek één geïsoleerde functie binnen een object testen. Voor meer informatie kan je hierover onze blog lezen.
Voorbeeld: een bezoeker vult een contactformulier in en krijgt vervolgens een e-mail met de bevestiging dat het versturen is gelukt
Alleen in dit simpele voorbeeld worden al talloze functies gebruikt. Denk hierbij aan het versturen van het contactformulier, het valideren van de input, de input in een database zetten en het versturen van de e-mail.
Om niet afhankelijk te zijn van andere functies kun je hier test doubles toepassen. Je wilt niet steeds een e-mail versturen als je alleen getest hebt dat de input valide is. Dat is ook niet de taak van de functie die de input valideert, en kan daarom vervangen worden door een test double. Deze test double luistert naar dezelfde aanroepen, maar in plaats van succesvol een e-mail te versturen en terug te sturen dat het gelukt is, doet deze alsof er een e-mail succesvol wordt verstuurd en stuurt vervolgens terug dat het gelukt is.
Er zijn verschillende variaties van test doubles. Hieronder zijn de drie meest gebruikte variaties beschreven.
Een fake is een object dat op dezelfde manier is geïmplementeerd als het echte object. Je gebruikt dus dezelfde aanroepmethode en geeft dezelfde parameters mee. Echter, een fake doet niet hetzelfde als het echte object. Je wilt bijvoorbeeld in een test niet steeds een externe API aanroepen, wat tijd of soms geld kost. Om toch de logica van je applicatie zoveel mogelijk na te bootsen kun je met een fake doen alsof je deze API aanroept. Door op één plek aan te geven dat een fake jouw echte object vervangt, hoef je je in tests niet af te vragen of je met het echte object of de fake communiceert. Dit heeft als voordeel dat je in je test geen specifieke test double logica hoeft te zetten. Waardoor je test eenvoudiger en leesbaarder blijft. Een nadeel is dat je twee (of meer) implementaties moet bijhouden, zonder dat je in je test direct ziet dat dit nodig is. En in het geval van een externe API kunnen er wijzigingen worden gemaakt die je niet opmerkt, omdat je met je eigen implementatie communiceert.
Een stub is een object dat voorgedefinieerde data bevat en deze gebruikt als een functie wordt aangeroepen. Een stub kan gebruikt worden als je geen database connectie wilt maken, maar toch data nodig hebt. Om bijvoorbeeld iets uit te rekenen, zoals een gemiddelde. De functie die een gemiddelde berekent, heeft natuurlijk data nodig. De functie die deze data ophaalt, moet vervolgens de data uit de stub teruggeven en het niet uit de database halen. Het maakt de functie die we testen namelijk niet uit waar de data vandaan komt, zolang de data maar komt. Vervolgens kan de functie gewoon uitgevoerd worden en kun je het resultaat verifiëren.
Een mock is een object dat registreert welke functies aangeroepen worden. Dit betekent dat de inhoud van de functie niet wordt uitgevoerd, maar dat je enkel kunt verifiëren dat de applicatieflow is zoals deze moet zijn. Vervolgens kun je aangeven wat je terug wilt krijgen van de functie, om de data die je nodig hebt om hetgeen je specifiek wil testen, ook terug te krijgen. Een voorbeeld is een beveiligingssysteem. Je wilt natuurlijk niet de hele tijd deuren sluiten tijdens een test. Hiervoor kun je een mock gebruiken. Deze mock vertelt dan of de functie is aangeroepen om de deur te sluiten, waardoor je kan verifiëren dat de functie van jouw test werkt. Dit betekent natuurlijk niet dat die functie ook daadwerkelijk de deur sluit. Maar dat maakt in deze test niet uit, want je test alleen of het “seintje” aankomt, niet de logica van de deur zelf. Dit gebeurt in een andere test.
Een fake kun je te allen tijde gebruiken ter vervanging van een echt object. Echter is het voor grote objecten niet altijd geschikt, omdat je meerdere implementaties bij moet houden. Dit kan zorgen voor veel extra werk, ook al is de code vaak een versimpelde variant op de code van het echte object.
Of je een stub of toch beter een mock kunt gebruiken, kun je beantwoorden door jezelf twee vragen te stellen:
Als het antwoord op beide vragen “nee” is kun je ervan uitgaan dat een stub geschikt is. In de andere gevallen kun je beter voor een mock kiezen.