Test Driven Development - toepassing op een project
![development](/_next/image?url=https%3A%2F%2Fcms.codana.be%2Fsites%2Fdefault%2Ffiles%2F2023-09%2Fpexels-pavel-danilyuk-5496461.jpg&w=1920&q=100)
Intro
Wat is TDD
TDD, of voluit Test Driven Development, is een aanpak van ontwikkeling waarbij we vertrekken van het schrijven van tests.
Alvorens we de code schrijven voor een bepaalde functionaliteit, bedenken we welk resultaat we precies verwachten bij het triggeren van een bepaalde actie en zetten we dit alvast klaar in een test.
Een voorbeeld hiervan is een date mapper functionaliteit. Wanneer je bepaalde input aan de mapper doorgeeft, verwacht je dat die er in een ander formaat uitkomt.
1
2
3
$mappedDate = DateMapper:AmericanDatetoDefaultDate('05/27/2005');
self::assertEquals('2005-05-27', $mappedDate);
Wanneer we deze test laten lopen, zal deze uiteraard falen en dat is ook de bedoeling.
De volgende stap is om een minimum aan code te schrijven om de test de doen slagen. Daarna voeren we de test opnieuw uit en dit proces herhalen we tot alle assertions succesvol zijn en er dus aan alle verwachtingen voldaan werd. Zodra de test succesvol gelopen heeft, kijken we na hoe we de code kunnen optimaliseren of vereenvoudigen. Is de test succesvol met een minimum aan code, dan schrijven we de volgende unit test uit.
Dit continue proces kan als volgt weergegeven worden:
Zoals in bovenstaande tekst en diagram aangegeven, werken we met unit tests. Het is bij TDD belangrijk om te vertrekken vanuit kleine geïsoleerde functionaliteiten die eenvoudig getest kunnen worden. In een later stadium kunnen er dan uiteraard ook integration tests toegevoegd worden.
Waarom kiezen voor TDD?
Test driven coderen heeft verschillende voordelen, zowel op korte als op lange termijn.
We vermelden hieronder de belangrijkste:
- Onderhoudbaarheid en schaalbaarheid
- Minimum aan code om een bepaald resultaat te behalen
- Hierdoor bestaat de code uit kleine stukken begrijpbare code
- Dat maakt het dan weer makkelijker om uitbreidingen toe te voegen
-
Return on investment
-
Op langere termijn zal de development tijd in verhouding heel wat lager zijn dan op een project dat niet volgens TDD ontwikkeld is
-
Door de falende tests die meteen naar boven komen, is de kans dat er bugs in het systeem sluipen veel kleiner. Deze worden immers al vroeg tijdens het development proces opgemerkt
-
De impact van changes op bestaande functionaliteiten is direct zichtbaar aan de hand van falende tests. De developer dient dus niet manueel verschillende testflows te doorlopen om de impact van zijn/haar tests na te gaan, wat in bepaalde gevallen een zeer tijdrovende taak kan zijn.
-
-
Vertrouwen
-
Als de code van de applicatie bij toevoegingen of aanpassingen steeds volledig en automatisch getest wordt, kan je gerust zijn dat de goede werking van de applicatie gewaarborgd blijft. Zo kan je als developer nieuwe functionaliteiten steeds vol vertrouwen lanceren.
-
Hoe implementeren we TDD bij Codana?
Bij Codana maken we gebruik van . In de pipeline definiëren we verschillende “stages” waarin er onder meer wordt nagekeken of de code voldoet aan de laatste standaarden. Dit gebeurt aan de hand van tools als codesniffer, psalm, grumphp, …
Daarnaast is het ook in deze pipeline dat we onze tests geautomatiseerd laten lopen bij elke merge request. Mergen is enkel mogelijk wanneer alle tests “groen” zijn en zo zijn we zeker dat alles netjes blijft werken, ongeacht de nieuwe features of changes.
Concrete implementatie
Calculatie tools
Onze klant Librius heeft voor hun ledenplatform “Libra” nood aan een berekeningsfunctionaliteit. Concreet dienen ze op basis van bepaalde input, de vergoedingen te berekenen voor alle uitgeverijen die lid zijn bij hen.
Deze berekeningen brengen heel wat complexiteit met zich mee en dus is het belangrijk dat deze goed getest zijn alvorens te implementeren in een volledige flow. Test driven ontwikkelen was dan ook de logische keuze.
Alvorens je kan starten met het schrijven van de tests, moet je natuurlijk over referentieresultaten beschikken. In dit geval was de beste optie om tot dergelijke referentieresulaten te komen, het “vertalen” van de functionaliteit naar een Excel bestand. Aan de hand van de formules die de klant ons aangeleverd had en een beperkte dataset (gebaseerd op real life data), stelden we een transparante Excel op waarin elke stap van de berekening apart vermeld wordt. Waar mogelijk, voegden we ook controlegetallen toe, die we later ook zullen kunnen verifiëren in onze tests (bvb. komt het totale percentage steeds op 100% uit).
Deze Excel file werd aan de klant bezorgd zodat zij alle stappen en resultaten kon nakijken om vervolgens finale goedkeuring te geven om de berekeningen op deze manier te implementeren.
And so the testing begins...
Met de Excel hebben we een goede leidraad om onze testen op te bouwen. Per stap ontwikkelen we een unit test volgens het principe dat eerder omschreven werd.
We isoleren de functionaliteit, bepalen welke resultaten we willen bekomen, zorgen ervoor dat de test faalt en schrijven de nodige code om een werkende test te bekomen.
Dit proces herhalen we tot alle unit tests succesvol zijn en alle tussenstappen correct berekend kunnen worden.
Bij deze unit tests maken we gebruik van exact dezelfde data als bij de vooropgestelde Excel. Op die manier kunnen we één op één nakijken of de resultaten correct berekend worden. Dit doen we door zowel de verwachte subresultaten te controleren als de controlegetallen. Als beide correct zijn en overeenkomen met de Excel, zijn we zeker dat de achterliggende code alles juist berekent.
Nadat alle unit tests voor een bepaalde berekening uitgewerkt werden, schrijven we een integration test die alle stappen na mekaar gaat uitvoeren zodat we kunnen nagaan of ook de gehele flow een juist eindresultaat oplevert.
Waar ligt hier nu juist het voordeel van test driven te werken? Er zijn talloze voordelen, maar we lichten er twee extra toe:
-
Zekerheid: aangezien het om complexe financiële berekeningen gaat, is het belangrijk om zeker te zijn van de juistheid van de resultaten. We willen zowel zelf volledig vertrouwen hebben in onze code als onze klant de garantie te kunnen geven van de juistheid van de resultaten. Specifiek in dit geval kunnen we zo ook kleine zaken als afrondingsfouten snel opsporen en corrigeren.
-
Onderhoudbaarheid: indien er in de toekomst gevraagd wordt een bepaalde aanpassing te doen aan een specifieke stap, kunnen we door middel van de tests zeer snel zien of deze aanpassing misschien ook andere stappen beïnvloedt en wat daar de mogelijke impact van is. Op die manier kunnen we onze code bijsturen zodat we de rest van de werking niet beïnvloeden
Import flows
Een andere belangrijke functionaliteit binnen de Libra tool is het titelbeheer en dan meer specifiek de titelimport. Titels kunnen op meerdere manieren geïmporteerd worden, hetzij manueel, hetzij automatisch, maar eender hoe ze het systeem binnenkomen, voor elke titel dienen er verschillende checks doorlopen te worden. Op basis daarvan worden er onder andere extra kenmerken op de titel toegevoegd.
De flow die doorlopen moet worden is vrij complex en bevat verschillende checks die noodzakelijk zijn voor mogelijke edge cases.
Om hier een zo compleet mogelijk overzicht van te krijgen, stellen we samen met de klant een flow chart op dat doorlopen zal worden voor elke titel.
Wanneer deze flow chart (theoretisch) volledig uitgewerkt is, nemen we enkele reële voorbeelden en doorlopen we stapsgewijs de flow om na te gaan of elke titel juist afgehandeld zou worden. Waar nodig sturen we de flow chart bij.
Nadat de flow chart op punt staat, gaan we aan de slag met het isoleren van bepaalde functionaliteiten, zoals bijvoorbeeld de "flaggers". Dit zijn kleine stukjes code die bepaalde controles gaan doen op doorgegeven waardes en op basis daarvan een true/false waarde terugsturen.
Eerst schrijven we weer kleine unit tests waarin we de verwachte resultaten valideren. Daarna schrijven we de functionaliteit van de mapper en passen we deze aan totdat we een succesvolle test bekomen. Zoals eerder vermeld, bekijken we, nadat de test slaagt, ook of we de code kunnen optimaliseren en vereenvoudigen.
Stapsgewijs kunnen we zo ook voor deze functionaliteit zorgen voor 100% test coverage.
Conclusie
Hoewel test driven ontwikkelen in het begin een tijdrovende uitdaging lijkt, merk je al snel de voordelen op korte en lange termijn. Zo zullen bugs al vroeg in het development proces naar boven komen en bestaat je implementatie uit kleinere leesbare stukken code die makkelijk te onderhouden zijn naar de toekomst toe.
Komen er toch issues naar boven in de toekomst, dan is de oorzaak ervan makkelijk te isoleren en op te sporen via de tests.