Redactie: Gerben de la Rambelje
Auteur: Chris Schotanus ● chris.schotanus@cgi.com ● @CSchotanus
Gebeurt het jullie ook wel eens? Dat je door het lezen van artikelen of blogs op gedachten wordt gebracht? Recentelijk kwam ik in een dergelijke situatie terecht. Deze riep bij mij wel wat vragen op en ik ben benieuwd wat jullie, als ervaren testers, hiervan vinden.
Wat was het geval? Ik ben een voorstander van Acceptance Test Driven Development (ATDD) en veel daaraan gerelateerde ‘DD’-aanpakken. Immers, zoals Bill Hetzel ooit zei: ‘One of the most effective ways of specifying something is to describe (in detail) how you would accept (test) it if someone gave it to you’.
Laatst las ik op internet een facebook note van Kent Beck, de grondlegger van Test Driven Developement (TDD), met de titel: ‘RIP TDD’[i]. In deze nogal cynische note vertelt Beck dat hij het heel jammer vindt dat TDD naar de schroothoop wordt verwezen door David Heinemeier Hansson[ii]. Ik ben natuurlijk ook gaan kijken naar het verhaal van Hansson en las daar inderdaad een aantal steekhoudende argumenten. En David verwees weer naar een artikel van James Coplien genaamd ‘Why unit testing is waste’ [iii]. Dus wat nu? Blijven we (geautomatiseerd) unittesten of niet?
Het voordeel van unittesten
Zowel vanuit testen in een lineair proces als vanuit de Agile wereld wordt, overigens met geheel verschillende redenen, graag de nadruk gelegd op het zo vroeg mogelijk testen. Bij lineair ontwikkelen wordt dat deels ingegeven vanuit de theorie van Boehm die zegt dat vroeg gevonden fouten goedkoper op te lossen zijn dan die, die in een later stadium worden gevonden. Bij Agile ontwikkelen is de reden vooral de snelle feedback loop die de ontwikkelaars ondersteunt bij het snel vinden en dus oplossen van fouten.
Uit ervaring weet ik dat het unittesten bij lineair ontwikkelen er nogal eens bij inschiet of dat het zo wordt gedaan dat de software ‘in ieder geval niet crasht’ (ik weet het, ik generaliseer). Wij testers hebben altijd geprobeerd om het unittesten op een hoger peil te krijgen teneinde te voorkomen dat tijdens de systeemtest problemen worden gevonden die met een unittest eenvoudig gevonden en hersteld hadden kunnen worden.
Geautomatiseerd unittesten
Nu loop ik al weer enige tijd rond bij bedrijven waar Agile wordt gewerkt en daar zie ik dat steeds vaker de test automation pyramid van Mike Cohn[vii] wordt gepromoot. Die toont waar volgens hem – en veel meer agilisten – de nadruk voor het testen zou moeten liggen: onder in de piramide. Maar is dat wel gebaseerd op de kwaliteit van testen? Of heeft dat idee een andere basis?
Ik denk dat die visie in eerste instantie is ontstaan door te kijken naar de complexiteit van het automatiseren van testen op de verschillende niveaus. De unittests zijn eenvoudiger te automatiseren dan tests op een (grafische) gebruikersinterface. Immers, bij een dergelijke gebruikersinterface zijn de traditionele testtools, als die niet slim worden gebruikt, gevoelig voor wijzigingen in die gebruikersinterface. Dat zorgt weer voor veel onderhoud van de testgevallen.
Unittesten bij Agile Development
Bij het automatiseren van de unittest worden frameworks gebruikt, waarbij in de source van de programmatuur de unittestgevallen worden vastgelegd. Deze worden continu gebruikt om, direct na het inchecken en compileren van de programmatuur, de code te testen. Het automatiseren van de unittest is relatief eenvoudig, geeft snelle feedback en is dus efficiënt. Met Agile ontwikkelen gebeurt daarom eigenlijk wat we bij lineair ontwikkelen al jaren graag willen: de nadruk wordt gelegd op de unittest. En als we ook nog Test Driven gaan ontwikkelen wordt het helemaal top, want dan wordt ook meteen de code goed gedocumenteerd. Slim toch? Of toch niet? Laten we eens verder kijken.
Test Driven Development
Bij TDD wordt voor elk stukje software dat een programmeur ontwikkelt eerst een testgeval gemaakt en bij de code vastgelegd. Pas als de code die door de programmeur wordt geschreven de test goed doorloopt en de test succesvol is, zal de programmeur een volgend teststap ontwerpen en vastleggen. Dan pas zal de programmeur de daadwerkelijke code schrijven en testen. Dit betekent dat er dus minstens zoveel testgevallen zijn vastgelegd als er acties in de code zijn gedefinieerd. Het resultaat van deze werkwijze is dat de code ‘schoon’ is (geen toeters en bellen bevat) en bijzonder goed onderhoudbaar en dus overdraagbaar is. Immers, de testgevallen vormen de documentatie en die testgevallen zijn onlosmakelijk aan de code verbonden. Maar wat als de code sterk wijzigt? Dan zal een aantal van die testgevallen een fout veroorzaken en dus ook aangepast moeten worden. Kijk eens naar het volgende voorbeeld (met dank aan Llewellyn Falco[ix]).
Stel, een robot die een ruimte ingaat moet er ook weer uitkomen. Als acceptatietest is het dan voldoende om (blackbox) te testen aan de buitenzijden van die ruimte. | |
Gaan we ook whitebox testen of de gang door de ruimte ook goed gaat, moeten we kijken naar de structuur binnen de ruimte, bijvoorbeeld op de snijpunten van een vlakverdeling. Dat levert in dit voorbeeld vijf unittestgevallen op. | |
Als dat het pad door de ruimte later bij onderhoud anders wordt gedefinieerd en bijvoorbeeld wegens een technische situatie via de wanden loopt in plaats van diagonaal levert dat bij acceptatie weinig extra testwerk op. We testen nog steeds aan de buitenzijden van de ruimte. | |
Qua unittest moeten in die situatie veel, zo niet alle unittestgevallen worden aangepast omdat de huidige, die op de snijpunten met de vlakverdeling, niet meer geldig zijn. |
Bij handmatig testen is dat niet zo’n probleem maar wanneer we alle unittestgevallen gebruiken als ontwerpdocumentatie en we die unittestgevallen in een unittest framework hebben vastgelegd betekent dat extra onderhoud. Dit kan wel eens zo oplopen dat het onderhoud van de unittestgevallen meer tijd vraagt dan het onderhoud aan de software zelf. En daar ontstaat de vraag of diepgaand, TDD gebaseerd unittesten wel zo nuttig is. Natuurlijk, als we aan een systeemtest gaan beginnen willen we graag dat alle fouten er al zoveel mogelijk uit zijn. Dat geldt vooral bij lineair ontwikkelen, waar de overgangen ontwikkelen/unittest/systeemtest groot zijn. Bij Agile ontwikkelen gebeurt het ontwikkelen en testen veel meer in een gezamenlijk proces en het testen op acceptatieniveau frequenter. Hier wordt veel meer op het niveau van services getest die over het algemeen een veel kleiner functioneel gebied afdekken.
Acceptance Test Driven Development
Om nu toch het principe toe te passen dat de testgevallen tevens documentatie zijn en dat het vooraf definiëren van testgevallen helpt bij het verkrijgen van betere specificaties, kunnen we Acceptance Test Driven Development (ATTD) of Specification By Example toepassen. Dit zijn met TDD vergelijkbare technieken maar dan toepasbaar op het service- en GUI-niveau. Bij ATDD zullen de individuele services blackbox worden getest, tegen vooraf gedefinieerde acceptatiecriteria (of acceptatietestgevallen). Ook hier geldt dus ook dat de testgevallen tevens de documentatie van de service vormen. Deze testgevallen zijn door het abstractieniveau echter veel minder gevoelig voor veranderingen. Immers, de robot uit het voorbeeld gaat nog steeds ergens binnen en komt aan de andere kant weer naar buiten; het pad door de ruimte is voor die test niet interessant.
Over het algemeen zijn de services van een geringere omvang dan de code die wordt opgeleverd bij grote systemen die vaak uit één stuk bestaan. Doordat die omvang geringer is zal het over het algemeen niet erg lastig zijn om bij het optreden van een fout de oorzaak daarvan te achterhalen. Hierdoor wordt de noodzaak van diepgaand TDD vanzelfsprekend minder. Natuurlijk zullen de programmeurs de code goed moeten testen en natuurlijk zullen zij daar frameworks voor geautomatiseerd testen bij gebruiken. Echter, wanneer we ATDD toepassen in plaats van TDD, wordt het beheer en onderhoud op de testgevallen veel minder, zonder dat de inzichtelijkheid in de kwaliteit van de opgeleverde service afneemt.
[i] Kent Beck: “RIP TDD” – https://www.facebook.com/notes/kent-beck/rip-tdd/750840194948847
[ii] David Heinemeier Hansson: “TDD is dead. Long live testing” – http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html
[iii] James Coplien: “Why unit testing is waste” – http://www.rbcs-us.com/documents/Why-Most-Unit-Testing-is-Waste.pdf
[vii] Mike Cohn: Succeeding with Agile: Software Development Using Scrum, ch 16
[ix] Llewellyn Falco: BDD vs TDD (explained) – https://www.youtube.com/watch?v=mT8QDNNhExg