Het schrijven van software-testen kost alleen maar geld

7f4534211bf2dc7aed58e1d2d827f400

Geschreven door Patrick Baselier op 16-12-2015
5 minuten leestijd

Bij Kabisa beschouwen we het bouwen van software als een vakmanschap. We streven er steeds naar om elk volgend product beter, robuuster en nog gebruiksvriendelijker te maken. Hiervoor gebruiken we verschillende technieken en methodieken. Behaviour-driven Development (BDD) en Test-driven Development (TDD) zijn er daar twee van; hierbij staat het schrijven van testen centraal.

Soms wordt ons de (terechte) vraag gesteld of we onze tijd niet beter kunnen besteden aan het schrijven van werkende software in plaats van geautomatiseerde tests. Vaak beslaan tests meer dan de helft van de geschreven code waarvan niks wordt gebruikt in een productiesysteem. Door het weglaten van tests zouden we dan eerder klaar zijn, waardoor het product vroeger live kan en de klant een stuk goedkoper uit is. Of toch niet?

Stel, we zijn bezig met de bouw een personeelsregistratiesysteem. Dit systeem stelt medewerkers in staat om verlofdagen in te voeren waardoor een manager kan zien wie op een bepaalde datum verlof hebben.

Personeelsregistratiesysteem

We bouwen dit in Ruby on Rails, een voor ons gangbaar open-source framework voor het ontwikkelen van webapplicaties. Voor het schrijven van testen maken we veelal gebruik van tools zoals Cucumber, Spinach en RSpec die naadloos aansluiten op Ruby on Rails.

BDD en integratietests

In de applicatie moet een manager kunnen zien welke medewerkers op welke datums verlof hebben. Onze BDD aanpak stelt ons in staat om deze requirement om te schrijven naar een test scenario in een vorm die ook voor niet-techneuten begrijpelijk is en de klant tevens vraagt na te denken over het belang van de feature.

Het test scenario zou er zo uit kunnen zien:

1
2
3
Given I have 30 employees of which 4 of them are on leave on 31-12-2015
When I select 31-12-2015 in the calendar
Then I see only these employees listed on the screen

Deze test beschrijft een stukje functionaliteit van het systeem en dankzij de vorm (de tests bestaan uit normaal leesbare Engelse zinnen) is het schrijven van tests (en daarmee in feite het uitschrijven van functionele requirements) niet een taak die alleen een programmeur kan uitvoeren, maar ook door een klant kan worden gedaan.

De technische uitwerking laten we achterwege, maar wanneer elke functionaliteit van het systeem op deze manier in een test wordt vastgelegd, levert dit meteen al een hoop voordelen op:

Terug naar de eerder geschreven test. Hier testen we een onderdeel van het systeem, sommigen noemen dit ook wel een end-to-end test of integratietest (al zijn hier subtiele verschillen tussen). Omdat we wel een test hebben, maar hiervoor nog geen functionaliteit hebben gebouwd, zal de test falen. Hoe nu verder?

De foute test zal ons een hint moeten geven wat we moeten doen. We beperken ons tot het schrijven van code die de foutmelding wijzigt of de test doet slagen (“Change the message”). Ons ontwikkelproces wordt dus in feite gestuurd door het gedrag wat we in de test hebben geschreven: Behaviour Driven Development.

Een applicatie is niet één bestand met oneindig veel regels code. Tijdens de bouw onderscheiden we verschillende componenten of modules (elk technologie heeft uiteraard z’n eigen jargon) met elk z’n eigen specialiteit. Zo zijn er componenten die alleen maar tekst op het scherm tonen of componenten die zich puur bezig houden met het ophalen van gegevens van medewerkers uit een database die op een gegeven datum verlof hebben.

TDD en unit-tests

Om een falende test verder te laten komen, zul je dus vroeg of laat een component moeten bouwen of de functionaliteit van een bestaand component moeten aanpassen. Op zo’n moment laten we de eerdere test even voor wat het is en concentreren we ons op het component. In plaats van het bouwen van een component of het toevoegen van een functie (of methode of property of… yep, weer dat jargon), gaan we ook hier weer met behulp van tests beschrijven wat we precies nodig hebben. Deze aanpak wordt TDD genoemd omdat hier de tests ons voorschrijven welke implementatie we moeten gaan bouwen.

Deze tests (ook wel unit-tests genoemd) zijn vaak technischer van aard, maar zijn voor een klant ook minder interessant; ze zijn met name bedoeld om te controleren of een radertje van een groter systeem z’n werk goed doet en blijft doen. Door met tests de functionaliteit van een component vooraf te beschrijven, waarbij de programmeur als taak heeft deze tests te laten slagen door de juiste implementatie te bouwen, schrijf je op deze manier ook niet meer code dan nodig. Daarmee voorkom je onnodig complexe programmacode waarin rekening wordt gehouden met allerlei fictieve scenario’s. Minder code betekent eerder klaar (en minder kosten voor de klant), overzichtelijker en daardoor beter onderhoudbaar.

Stel dat we een component hebben die ook een lijst met medewerkers retourneert die op een gegeven datum verlof hebben. Een test zou er zo uit kunnen zien:

1
2
3
4
5
6
7
8
before do
  create_5_employees_with_leave
  create_10_more_employees_without_leave
end

it 'returns a list with employees on leave' do
  expect(LeaveManager.where(on: Date.today)).to have(5).items
end

Scherm met testen

In werkelijkheid zal de test uitgebreider zijn en zal het aantal tests ook groter zijn, maar voor een (Ruby-)programmeur levert dit genoeg handvatten om de implementatie te gaan maken. Het geeft hem/haar ook meer vertrouwen. Wanneer de tests slagen, is de implementatie gereed. Vervolgens kan worden bekeken of de code beter kan; misschien kan een if-then-else constructie slimmer of kan een ander algoritme worden geprobeerd. Zolang de tests blijven slagen, doet de code wat het moet doen. In de programmeurswereld ook wel bekend als Red-Green-Refactor: schrijf eerst een falende test (Red), vervolgens de code om de test te laten slagen (Green) om uiteindelijk de code te optimaliseren (Refactor).

Red-Green-Refactor

Is een applicatie met testen nu duurder of juist goedkoper?

Een applicatie mét testen is in ieder geval betrouwbaarder. Door de mogelijkheid om ten alle tijden de geautomatiseerde testen uit te kunnen voeren, is meteen, en dus niet pas achteraf wanneer het live staat, inzichtelijk te maken welke functionaliteit wel of niet meer werkt. Daarnaast beschrijven de testen voor zowel klant als programmeur het systeem omdat ze tevens dienen als een verzameling van requirements, maar ook als documentatie van individuele componenten. Met testen voorkom je dat er dode code wordt geschreven; functionaliteit waarvoor de klant wel moet betalen, maar nooit wordt gebruikt.

Het schrijven van testen maakt een applicatie dus juist goedkoper.

Wil je meer weten of ben je geïnteresseerd laat dan een bericht achter in de comments.

7f4534211bf2dc7aed58e1d2d827f400

Patrick Baselier

Professionele Ruby on Rails-, front-end en wat minder professionele (of noem het hobbymatig) Ember developer, die houdt van kennisdeling en ooit het niveau van beginnende gitarist hoopt te ontstijgen.

GitHub: bazzel • Twitter: @patrickbaselier