Unit testing

Insights

Hoe goede unit testen schrijven: 8 tips

🛠 Frontend development

Er is geen twijfel dat het unit testen van een applicatie de kwaliteit ervan verhoogt.

Goede unit testen geven developers vertrouwen bij het implementeren van nieuwe functies. Ze weten wanneer ze bestaande functionaliteit hebben gebroken en lossen het op voordat deze bugs naar de productie gaan. Aan de andere kant kan een slechte test een ontwikkelaar het leven zuur maken en veel tijd verspillen aan onderhoud en het oplossen van problemen met de unit testen zelf.


Goede unit testen schrijven is geen raketwetenschap 🚀. Het is gewoon code. Volg de best practices en terwijl je meer ervaring opdoet ga je je ook meer vertrouwd voelen. En wie weet ga je het ook nog leuk vinden. Want we zullen even eerlijk zijn unit testen is niet het fijnste deel van de job maar ze zijn o zo belangrijk om de kwaliteit van product hoog te houden.


We behandelen enkele tips die je hopelijk in de juiste richting leiden en om het draagbaarder te maken. Op het einde van het artikel zijn ook nog voorbeelden van unit testen moest je niet de tijd hebben om alles te lezen. 😉

🔬 Test kleine stukjes code in isolatie

In tegenstelling tot integratietesten, waarbij je doel is om ervoor te zorgen dat complete acties werken in een bijna productieomgeving, moeten unit testen je helpen om snel kleine stukjes code te isoleren en te testen. Daarom moeten die functies en klassen niet afhankelijk zijn van iets anders dan mocks en stubs.


Hoe meer logica je probeert te dekken in je testen, hoe moeilijker het is om een volledige dekking te hebben en om de exacte oorzaak van het falen te vinden. We pleiten er niet voor dat je absoluut alles moet mocken en stubben. Het is prima om een functie te testen die afhankelijk is van drie andere functies die ook met unit testen worden afgedekt. Als er iets stuk gaat, zullen die testen wel allemaal falen, en kun je het probleem snel opsporen. Dit proces wordt echter moeilijker, want als meerdere testen falen, moet je eerst uitzoeken wat de oorzaak is.

🍺 Volg de AAA en niet de AA

De AAA is een algemene aanpak voor het schrijven van beter leesbare unit testen.

AAA staat voor Arrange, Act en Assert.


In de eerste stap regel je de dingen voor het testen. Dit is waar je variabelen instelt, objecten instantieert, en de rest van de vereiste instellingen doet om de test uit te voeren. Tijdens deze stap definieer je ook al het verwachte resultaat. Dit biedt enkele voordelen. Ten eerste word je gedwongen het uit te zoeken voordat je de geteste logica aanroept. Ten tweede is het handiger om de verwachte uitvoer direct na de invoer te zien en niet te vermengen met de rest van de code.


Dan handel je. Hier roep je de geteste functie aan en sla je de resultaten ervan op. Nadat je de resultaten hebt, is het tijd voor te kijken of wij de verwachten data hebben kunnen ontvangen.


Nu stel je vast of de hypothese juist is. Dit is de essentie van een unit test, omdat dit deel uiteindelijk iets test. Het doel is om te controleren of het verkregen data overeenkomt met het verwachte resultaat. Unit test frameworks hebben speciaal daarvoor diverse assertiemethoden die soms matchers worden genoemd. Unit testen zijn er niet alleen om functies te testen maar ook voor veranderingen in de UI te kunnen testen

📏 Hou de testen zo kort mogelijk

Korte functies zijn veel gemakkelijker te lezen en te begrijpen. Omdat we één stukje logica per keer testen, zouden de testen toch niet langer moeten zijn dan een paar regels code.


Soms echter kan de regellogica behoorlijk complex zijn. Hoewel het een teken kan zijn dat er iets mis is met het code zelf, kan high level logica meerdere dependencies hebben die nogal wat boilerplate code vereisen om de mocks en stubs te initialiseren.


Vermijd het overal kopiëren van deze spaghetti. Unit testen verschillen niet veel van gewone code, dus het DRY (don't repeat yourself) principe is van toepassing. Vergeet niet dat je ze in de toekomst toch moet onderhouden.

🤓 Maak de testen zo simpel mogelijk

Vermijd complexe logica in de testen. Die wil je toch niet testen. 😅


Het is verleidelijk om een hoop generieke logica te schrijven die de testcode nog verder inkort en vanuit DRY-perspectief goed lijkt. Maar als er iets stuk gaat, zul je deze logica vaak samen met de test moeten debuggen om het probleem te vinden.


Naarmate het project complexer wordt, werkt generieke code misschien niet meer voor alle scenario's en zou ook de complexiteit toenemen om ze allemaal aan te passen. Net als alle andere code kunnen ook testen gerefactored worden. Je zult een goede balans moeten vinden tussen eenvoud en herhaling.


In zeldzame gevallen kunnen testen natuurlijk behoorlijk complex worden. Misschien heb je aangepaste assert functies nodig om de resultaten te testen of een klein framework voor het testen van soortgelijke functionaliteit.

🏎️ Zorg dat je testen snel zijn

Unit testen moeten op elke machine kunnen draaien. Je team zou ze meerdere keren per dag moeten uitvoeren. Ze draaien zowel tijdens lokale builds als in je CI. Je wilt dat ze snel draaien want als we eerlijk zijn ons concentratiespanne als developer is niet hoog of toch niet bij mij. 😅


Zorg ervoor dat je alle externe dependencies spot die de test kunnen vertragen, zoals een api call doen, databases of toegang tot het bestandssysteem. Die zijn bijna onmogelijk deterministisch te maken. Vermijd waits en timeouts in je testen voor zover het mogelijk is. Zelfs als je een time-out test, overweeg dan om ze extreem kort te maken, slechts een paar milliseconden. Bij het testen van async condities is een goed idee om dit te mocken dan blind te wachten op een resultaat.

💾 Hou je unit test stateless

Je testen mogen niets veranderen buiten hun scope of neveneffecten hebben. Als je testen alleen slagen als ze in een bepaalde volgorde worden uitgevoerd, is er iets mis met hen of met de geteste code. Normaal zouden wij als Frontend developers de Backend de schuld geven maar dat is geen optie in deze situatie dus moeten wij het zelf oplossen. Testen moeten onafhankelijk zijn van elkaar. Moderne test frameworks draaien ze standaard parallel, dus je moet niet vertrouwen op de globale variabelen of de neveneffecten van de vorige test. Dat is een van de redenen waarom het gebruik van globale variabelen als een slechte praktijk wordt beschouwd.


Als je een complexe repetitieve opstelling nodig hebt, gebruik dan de setup en teardown mechanismen die het framework biedt. Die lopen gegarandeerd voor en na elke test of de hele suite. Op die manier zou je test deterministisch zijn wanneer hij individueel of als onderdeel van de hele suite wordt uitgevoerd. De volgorde zou geen verschil mogen maken.

📐 Voorkeur aan nauwkeurige test

Er is een reden waarom test frameworks verschillende assertiemethoden bieden. Ze bieden verschillende manieren om het resultaat te controleren, en ze tonen ook meer specifieke foutmeldingen wanneer een assertie faalt, zodat er meer context is om te zien wat er mis is. Natuurlijk zijn er ook meerdere methoden die ja gebruiken om het zelfde af te testen maar dan is het aan ons om het meest specifieke te vinden.


Frameworks bieden ook verschillende asserties voor verschillende manieren van testen. In Jest bijvoorbeeld test toBe op exacte gelijkheid met behulp van Object.is, terwijl toEqual en toStrictEqual recursief controleren of de objecten hetzelfde type en dezelfde structuur hebben.

🤖 Automatisch testen uitvoeren

Developers checken vaak de testen terwijl ze code schrijven om te weten of nieuwe wijzigingen geen problemen geven bij de de nieuwe functionaliteit. Als garantie moeten de testen echter ook automatisch draaien bij elke build. Ze moeten deel uitmaken van je continue integratieproces, en mislukte testen moeten worden behandeld als een buildfout. Iemand moet het onmiddellijk repareren.


Om te voorkomen dat code met falende testen in het repository komt, kun je overwegen testen te triggeren op git push. Voor JavaScript en TypeScript projecten kun je dat instellen met husky.

🙏 Voorbeelden van een unit test

We zullen starten met een heel simpele test die ik heb geschreven om een Vue component af te testen. Wat ook wel handig is aan unit testen is dat ze bijna bij iedere framework het zelfde zijn. Ze zullen niet 💯 % het zelfde zijn maar je kan ze sowieso lezen en begrijpen.

Dependencies

Chack

Nuxt 3

Chack

Vue test utils

Chack

Vite

Chack

Vitetest

nuxt

Deze test gaat kijken of mijn IconLink component dat ik gemaakt heb of dat een label heeft gerenderd. Het is belangrijk om dit te testen omdat het component als het een label meekrijgt als prop ook deze moet renderen en niet alleen het icoontje.

story
  • 1. De test krijgt een goede beschrijvende naam. Ik doe dit altijd volgens de “it Should” manier die door veel mensen gebruikt word. De bedoeling is te omschrijven wat de test gaat testen in een korte en duidelijke manier.

  • 2. In stap twee mounten we ons component zodat wij het geïsoleerd kunnen testen en we geven ook de juiste data mee zodat het label getoond word.

  • 3. Stap 3 is in het gemounted component opzoek gaan of data-el=”label” inderdaad in de html staat. Dit hoeft zeker en vast niet met een attribuut te werken je kan ook opzoeken op basis van class, id en tags.

  • 4. De laatste stap is kijken of de paragraaf inderdaad word terug gevonden, maar dit is niet genoeg op zijn eigen want er moet ook nog gekeken worden of die dan ook de juiste tekst toont als label.

simple unit test explained

Voorbeeld voor het mocken van een async functionaliteit

Ik ga een voorbeeld laten zien hoe ik een async composable heb gemocked waardoor de testen konden uitgevoerd worden zonder te wachten en met mocked data.

We gaan in dit voorbeeld een lijst component aftesten die onze content uit Storyblok gaat halen voor te tonen op de website. Storyblok is de Cms dat wij gebruiken nu in dit voorbeeld maar dit kon evengoed een api call zijn naar eender waar.

async component

Je ziet dat wij het zelfde doen als daarnet om ons component te mounten alleen doen wij er een flushPromises() functie achter van Vue test utils en die zorgt ervoor dat eerst alle async functies in onze script tag gaan geresolved worden. Dit zou perfect werken maar herinner je dat ik net heb gezegd dat we snel willen gaan. 🏎️

Dus we gaan onze composable mocken met vi.stubGlobal wat heel makkelijk gaat. Je geeft de naam van het gene wat je wilt mocken in en dan een functie wat hij moet terug geven. Wij verwachten in dit geval een object met de key content en content is eigenlijk een array van componenten maar voor het voorbeeld heb ik er even een object in geplaatst. Ik heb de verwachten uitkomst in een aparte json bestand staan om mijn test bestand zo clean en klein mogelijk te houden. Je ziet dan ook dat je in de assertiemethoden ook je json bestand kan gebruiken om in dit geval in de toBe functie te vergelijken.

asyn test

Conclusie

Ik ben zeker en vast ook geen expert in testen schrijven 😅 maar dit zijn wel puntjes die mij geholpen hebben om het concept beter te begrijpen en betere testen te schrijven.
Ik hoop dat dit artikel toch ook een beetje heeft kunnen helpen met wat testen is en om te laten zien dat testen niet complex is. Het neemt natuurlijk tijd in maar zeker in grotere projecten ga je het je niet beklagen later.

🎙

Testen zijn vrienden maar wel de vrienden die je liever niet te veel rond je hebt.

JeremyJeremy Mees
Jeremy

Jeremy

Delen:

Op zoek naar inspiratie?

Wij zorgen voor gepast advies op het vlak van frontend development.

💌

Insights

Meer inzichten

conference

🛠 Frontend development

Vue Amsterdam 2024: dag 1

Dag 1 van de jaarlijkse Vue Amsterdam conference.

A person holding a smartphone

📱 PWA,
🛠 Frontend development

PWA. Nu meer dan ooit.

De kloof tussen native en web wordt steeds kleiner. Dat bewijst ook de komst van Push Notifications voor web apps op het Home Screen (PWA’s) in iOS 16.4. Deze functionaliteit was al langer beschikbaar op Android, maar nu kunnen ontwikkelaars dit eindelijk voor beide platforms implementeren. Android en iOS samen hebben een marktaandeel van maar liefst 99% van alle mobiele gebruikers.

nuxt-gsap

💫 Motion,
🛠 Frontend development

Motion Library

Om een aantrekkelijke en moderne UI te bekomen, is het animeren van een website van groot belang. Daarom hebben we bij Appeel gewerkt aan een animatie project “Motion library”. In dit project worden verschillende componenten van een website geanimeerd en de code ervan getoond om zo als inspiratiebron te dienen voor alle collega’s van Appeel.