Tuple Logo

SHARE

Wat is Unit Testing? Minder bugs, betere code

sefa-senturk
Sefa Şentürk
2025-03-10 11:35 - 15 minuten
Software Development

Unit testing is een fundamentele techniek in softwareontwikkeling waarbij individuele componenten van een application, ook wel 'units' genoemd, worden getest om te controleren of ze correct functioneren. Door unit tests te gebruiken, kunnen ontwikkelaars fouten vroegtijdig opsporen en voorkomen dat bugs later in het ontwikkelingsproces grote problemen veroorzaken.

Wat is unit testing?

Unit testing is een softwaretestmethode waarbij afzonderlijke stukjes code, zoals functies, methoden of classes, geïsoleerd worden getest om te controleren of ze naar verwachting werken. Dit gebeurt doorgaans met geautomatiseerde testframeworks, zoals JUnit voor Java, PyTest voor Python en NUnit voor .NET.

Waarom is unit testing belangrijk?

Unit testing biedt verschillende voordelen die bijdragen aan de kwaliteit en stabiliteit van software:

Hoe werkt unit testing?

Unit testing werkt door afzonderlijke onderdelen van een applicatie te isoleren en te testen op correctheid. Dit wordt meestal gedaan met behulp van testframeworks die geautomatiseerde tests uitvoeren.

De basisprincipes van unit testing

Unit tests volgen een eenvoudig patroon:

Dit staat bekend als het AAA-principe (Arrange, Act, Assert) en helpt bij het structureren van duidelijke en begrijpelijke tests.

Hoe worden unit tests uitgevoerd?

Unit tests kunnen op verschillende manieren worden uitgevoerd:

Hoe zien unit tests eruit?

Hier is een eenvoudig voorbeeld van een unit test in Python met het framework unittest:

import unittest

def add(a, b):
    return a + b

class TestMathOperations(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)
        self.assertEqual(add(-1, 1), 0)
        self.assertEqual(add(0, 0), 0)

if __name__ == '__main__':
    unittest.main()

Uitleg van de test:

Unit testing strategieën

Om unit tests effectief in te zetten, gebruiken ontwikkelaars verschillende strategieën. Een goede unit test, dekt niet alleen de normale werking van een functie, maar test ook randgevallen en foutafhandeling.

Logische controles

Logische controles testen of een functie de verwachte uitkomsten produceert op basis van de gegeven invoer. Dit helpt bij het detecteren van programmeerfouten en incorrecte logica.

Voorbeeld:

def is_even(number):
    return number % 2 == 0

import unittest

class TestEvenFunction(unittest.TestCase):
    def test_is_even(self):
        self.assertTrue(is_even(4))
        self.assertFalse(is_even(3))

if __name__ == '__main__':
    unittest.main()

Hier wordt getest of is_even(4) True retourneert en is_even(3) False retourneert.

Grenstests

Grenstests controleren hoe een functie omgaat met minimale, maximale en buiten de grens liggende invoerwaarden. Dit voorkomt problemen bij extreme of onvoorziene inputs.

Voorbeeld:

Een functie die een getal deelt door een ander getal moet correct omgaan met nulwaarden.

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

class TestDivideFunction(unittest.TestCase):
    def test_divide(self):
        self.assertEqual(divide(10, 2), 5)
        self.assertEqual(divide(-10, 2), -5)
    
    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            divide(10, 0)

if __name__ == '__main__':
    unittest.main()

Deze test controleert of de functie correct omgaat met negatieve getallen en een fout gooit bij delen door nul.

Foutafhandeling

Het testen van exception handling is belangrijk om te controleren of de code goed omgaat met fouten.

Voorbeeld:

def safe_list_access(lst, index):
    try:
        return lst[index]
    except IndexError:
        return "Index out of range"

class TestSafeListAccess(unittest.TestCase):
    def test_valid_index(self):
        self.assertEqual(safe_list_access([1, 2, 3], 1), 2)
    
    def test_invalid_index(self):
        self.assertEqual(safe_list_access([1, 2, 3], 5), "Index out of range")

if __name__ == '__main__':
    unittest.main()

Deze test controleert of een lijst-index buiten bereik netjes wordt afgehandeld zonder dat de applicatie crasht.

Objectgeoriënteerde checks

Bij objectgeoriënteerde programmeertalen testen unit tests vaak methoden binnen een klasse.

Voorbeeld:

class BankAccount:
    def __init__(self, balance=0):
        self.balance = balance

    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
        else:
            raise ValueError("Deposit amount must be positive")

class TestBankAccount(unittest.TestCase):
    def test_deposit(self):
        account = BankAccount(100)
        account.deposit(50)
        self.assertEqual(account.balance, 150)

    def test_negative_deposit(self):
        account = BankAccount()
        with self.assertRaises(ValueError):
            account.deposit(-10)

if __name__ == '__main__':
    unittest.main()

Hier testen we of stortingen correct worden verwerkt en of een negatieve storting een foutmelding genereert.

Voorbeeld van een unit test

Laten we een concreet voorbeeld bekijken om te begrijpen hoe unit testing werkt in de praktijk. We gebruiken Python en het unittest-framework om een simpele functie te testen.

Een eenvoudige unit test in Python

Stel dat we een functie hebben die controleert of een woord een palindroom is (een woord dat hetzelfde blijft als je het omdraait).

def is_palindrome(word):
    return word.lower() == word.lower()[::-1]

Om deze functie te testen, schrijven we een unit test:

import unittest

class TestPalindromeFunction(unittest.TestCase):
    def test_palindrome(self):
        self.assertTrue(is_palindrome("racecar"))
        self.assertTrue(is_palindrome("level"))
        self.assertFalse(is_palindrome("hello"))
    
    def test_case_insensitive(self):
        self.assertTrue(is_palindrome("Madam"))

    def test_empty_string(self):
        self.assertTrue(is_palindrome(""))

if __name__ == '__main__':
    unittest.main()

Wat gebeurt hier?

Wanneer we dit script uitvoeren, controleert unittest of de functie correct werkt. Als een test faalt, krijgen we een foutmelding met details over wat er misging.

Uitbreiding: unit tests met parameters

Soms willen we meerdere invoerwaarden testen zonder steeds nieuwe testmethodes te schrijven. Hiervoor kunnen we pytest gebruiken met een parameterized test.

import pytest

@pytest.mark.parametrize("word, expected", [
    ("racecar", True),
    ("hello", False),
    ("Madam", True),
    ("", True)
])
def test_is_palindrome(word, expected):
    assert is_palindrome(word) == expected

Met pytest.mark.parametrize kunnen we dezelfde functie testen met verschillende inputs in één testmethode. Dit maakt de testcode overzichtelijker.

Voordelen van unit testing

Unit testing biedt talloze voordelen die bijdragen aan een efficiënter ontwikkelingsproces en een stabielere codebase. Hieronder bespreken we de belangrijkste voordelen.

Efficiënt fouten opsporen

Unit tests helpen ontwikkelaars om fouten vroeg in het ontwikkelingsproces te detecteren. Dit voorkomt dat bugs pas later in het proces worden ontdekt, waar ze duurder en tijdrovender zijn om op te lossen.

Voorbeeld:

Stel dat je een rekenfunctie schrijft en er later achter komt dat het delen door nul niet correct wordt afgehandeld. Een unit test kan dit probleem direct signaleren, zodat het niet doorstroomt naar productie.

Betere documentatie van code

Unit tests fungeren als een soort 'levende documentatie' van de code. Nieuwe ontwikkelaars kunnen aan de hand van de tests begrijpen hoe functies en methodes bedoeld zijn om te werken.

Voorbeeld:

Een test als deze laat direct zien wat een functie doet en welke invoerwaarden verwacht worden:

def add(a, b):
    return a + b

class TestMathOperations(unittest.TestCase):
    def test_add(self):
        self.assertEqual(add(2, 3), 5)  # Show that 2 + 3 should be 5

Nieuwe teamleden kunnen deze test lezen en begrijpen dat add(a, b) getallen bij elkaar optelt.

Snellere ontwikkelcycli en efficiëntere debugging

Met unit tests kunnen ontwikkelaars wijzigingen aanbrengen in hun code zonder bang te zijn dat ze bestaande functionaliteit breken. Dit versnelt de ontwikkelcyclus en minimaliseert het aantal regressiefouten (bugs die opnieuw verschijnen na een wijziging).

Voorbeeld:

Geschikt voor Agile en DevOps

Unit testing past perfect binnen Agile- en DevOps-omgevingen, waarin snelle iteraties en frequente releases essentieel zijn.

Veel DevOps-teams gebruiken CI/CD-pipelines waarin unit tests automatisch worden uitgevoerd bij elke codewijziging. Dit voorkomt dat defecte code in productie terechtkomt.

Hoe ontwikkelaars unit tests gebruiken

Unit testing kan op verschillende manieren worden toegepast binnen het ontwikkelingsproces.

Test-driven development (TDD)

Test-driven development (TDD) is een aanpak waarbij tests worden geschreven voordat de daadwerkelijke code wordt geïmplementeerd. Het proces bestaat uit drie fasen:

Voorbeeld van TDD:

Stel dat we een functie nodig hebben die controleert of een getal even is.

  1. Schrijf de test:

    import unittest
    from mymodule import is_even
    
    class TestIsEven(unittest.TestCase):
        def test_even_numbers(self):
            self.assertTrue(is_even(2))
            self.assertTrue(is_even(4))
        
        def test_odd_numbers(self):
            self.assertFalse(is_even(3))
            self.assertFalse(is_even(5))
  2. Voer de test uit: De test faalt, want is_even() is nog niet geïmplementeerd.

  3. Schrijf de functie:

    def is_even(n):
        return n % 2 == 0
  4. Voer de test opnieuw uit: De test slaagt.

Door deze aanpak zorgt TDD ervoor dat functies precies doen wat ze moeten doen, zonder overbodige complexiteit.

Unit testing na het afronden van code

Niet alle ontwikkelaars gebruiken TDD. In veel gevallen worden unit tests pas geschreven nadat een codeblok is voltooid. Dit wordt gedaan om te controleren of de functie correct werkt en blijft werken bij toekomstige wijzigingen.

Voordelen:

Nadelen:

Efficiëntie binnen DevOps en CI/CD

In DevOps-omgevingen worden unit tests vaak geïntegreerd in Continuous Integration (CI) pipelines. Dit betekent dat bij elke wijziging in de code automatisch tests worden uitgevoerd.

Een typische CI/CD-pipeline met unit tests bevat:

Code commit → Unit tests draaien automatisch → Code wordt alleen gedeployed als alle tests slagen

Voorbeeld van een GitHub Actions workflow met unit tests:

name: Run Unit Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.9
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run tests
        run: pytest

Dit zorgt ervoor dat elke codewijziging direct wordt getest, zodat fouten vroegtijdig worden gedetecteerd.

Wanneer is unit testing minder nuttig?

Hoewel unit testing een krachtige techniek is, zijn er situaties waarin het minder effectief of zelfs onpraktisch kan zijn. Hieronder bespreken we enkele van deze scenario's.

Wanneer tijd een beperkende factor is

Unit testing kost tijd, vooral bij strakke deadlines of snel veranderende projecten. In sommige gevallen kan het praktischer zijn om alleen de kritiekste functies te testen in plaats van volledige testdekking na te streven.

Voorbeeld:

Oplossing:

Een balans vinden tussen testdekking en snelheid door alleen de kernfunctionaliteiten te testen.

Bij UI/UX-gerichte applicaties

Unit tests zijn niet geschikt voor het testen van gebruikersinterfaces (UI) en gebruikerservaringen (UX). Dit komt doordat UI-tests afhangen van visuele elementen en interacties die moeilijk met geautomatiseerde unit tests te valideren zijn.

Voorbeeld:

Oplossing:

Gebruik UI-testframeworks en user acceptance testing (UAT) naast unit tests.

Legacy codebases zonder tests

Oude codebases bevatten vaak geen unit tests en kunnen slecht gestructureerd zijn, waardoor het moeilijk is om tests achteraf toe te voegen zonder de code ingrijpend te wijzigen.

Problemen:

Oplossing:

Bij snel evoluerende vereisten

Wanneer de specificaties van een applicatie voortdurend veranderen, kunnen unit tests snel verouderd raken. Dit leidt tot extra onderhoudswerkzaamheden en tijdverlies.

Voorbeeld:

Oplossing:

Best practices voor unit testing

Om unit testing effectief in te zetten, is het belangrijk om een aantal best practices te volgen. Hieronder bespreken we de belangrijkste richtlijnen voor goed gestructureerde en efficiënte unit tests.

Gebruik een unit test framework

Het gebruik van een framework zorgt voor gestructureerde en herhaalbare tests. Populaire frameworks zijn:

Met een framework kunnen tests eenvoudig worden uitgevoerd, gestructureerd en geïntegreerd in CI/CD-pipelines.

Voorbeeld van een test met PyTest:

import pytest

def multiply(a, b):
    return a * b

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 6),
    (-1, 5, -5),
    (0, 10, 0)
])
def test_multiply(a, b, expected):
    assert multiply(a, b) == expected

Hiermee kunnen meerdere testgevallen in één test worden gecombineerd.

Automatiseer unit testing

Handmatige tests zijn foutgevoelig en tijdrovend. Door unit tests te automatiseren in een CI/CD-pipeline, kan nieuwe code direct worden getest voordat deze wordt samengevoegd.

Voorbeeld van een geautomatiseerde test workflow met GitHub Actions:

name: Run Unit Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Install dependencies
        run: pip install -r requirements.txt
      - name: Run tests
        run: pytest

Elke keer dat code wordt gepusht, worden de tests automatisch uitgevoerd.

Houd tests kort en specifiek

Unit tests moeten één enkele functionaliteit testen. Dit maakt het makkelijker om fouten te lokaliseren en zorgt ervoor dat tests eenvoudiger te onderhouden zijn.

Slecht voorbeeld (te breed getest):

def test_math_operations():
    assert add(2, 3) == 5
    assert multiply(2, 3) == 6
    assert divide(6, 3) == 2

Als deze test faalt, is het onduidelijk welke functie het probleem veroorzaakt.

Goed voorbeeld (gescheiden tests):

def test_add():
    assert add(2, 3) == 5

def test_multiply():
    assert multiply(2, 3) == 6

def test_divide():
    assert divide(6, 3) == 2

Elke test focust op één functie, waardoor fouten makkelijker te achterhalen zijn.

Houd tests onafhankelijk

Elke unit test moet losstaan van andere tests. Tests die afhankelijk zijn van externe services of databases kunnen leiden tot onbetrouwbare resultaten.

Slecht voorbeeld (afhankelijk van database):

def test_get_user():
    user = db.get_user(1)  # Database connection required
    assert user.name == "John Doe"

Als de database niet beschikbaar is, mislukt de test.

Goed voorbeeld (mocking gebruiken):

from unittest.mock import Mock

def test_get_user():
    db = Mock()
    db.get_user.return_value = {"id": 1, "name": "John Doe"}
    assert db.get_user(1)["name"] == "John Doe"

Door een mock te gebruiken, blijft de test betrouwbaar en onafhankelijk van externe systemen.

Regelmatige testuitvoering

Unit tests moeten niet alleen bij oplevering worden uitgevoerd, maar regelmatig en bij elke wijziging in de code. Dit voorkomt dat fouten onopgemerkt blijven.

Manieren om dit te automatiseren:

Unit testing versus andere testvormen

Unit testing is slechts één van de vele soorten softwaretests. Om een goed begrip te krijgen van waar unit testing past in het grotere geheel, vergelijken we het met andere populaire testmethodes.

Unit testing vs. QA-testing

Belangrijk verschil: Unit testing richt zich op kleine code-eenheden, terwijl QA-testing de software als geheel test, inclusief gebruiksvriendelijkheid en edge cases.

Unit testing vs. functionele testing

Belangrijk verschil: Functionele tests controleren of de applicatie werkt zoals verwacht vanuit een gebruikersperspectief, terwijl unit tests kijken naar de interne werking van code.

Wanneer gebruik je unit testing en wanneer andere testvormen?

Gebruik unit testing voor:

Gebruik QA en functionele testing voor:

Unit testing is essentieel

Unit testing is een onmisbaar onderdeel van softwareontwikkeling. Door kleine, afzonderlijke code-eenheden te testen, helpt het bij het vroegtijdig opsporen van bugs, het verbeteren van codekwaliteit en het versnellen van ontwikkelcycli. Het past goed binnen Agile- en DevOps-processen en is een krachtige tool om software stabiel en onderhoudbaar te houden.

Hoewel unit testing niet altijd de beste oplossing is – zoals bij UI/UX-tests of snel veranderende codebases – blijft het een van de effectiefste manieren om software betrouwbaarder te maken. Door de juiste strategieën en best practices toe te passen, kan unit testing de algehele softwarekwaliteit sterk verbeteren.

Wil je meer weten over unit testing of heb je hulp nodig bij het implementeren ervan? Neem vrijblijvend contact met ons op en ontdek hoe we je kunnen helpen met testautomatisering en softwarekwaliteit.

Veelgestelde vragen
Wat wordt bedoeld met unit testing?

Unit testing is een testmethode waarbij individuele componenten van software (zoals functies of methodes) geïsoleerd worden getest om te controleren of ze correct werken. Dit wordt meestal gedaan met geautomatiseerde testframeworks.


Wat is unit testing vs. QA-testing?

Unit testing richt zich op het testen van kleine code-eenheden en wordt uitgevoerd door ontwikkelaars. QA-testing (Quality Assurance) controleert het hele systeem en wordt vaak door een aparte testafdeling uitgevoerd.


Wat is unit testing vs. functionele testing?

Unit testing test individuele functies of methodes, terwijl functionele testing controleert of de software als geheel correct werkt volgens de specificaties. Functionele tests bootsen gebruikersinteracties na en testen complete workflows.


Wat zijn de 3 A’s van unit testing?

De 3 A’s van unit testing zijn: Arrange – Zet de testomgeving op en initialiseer de benodigde data. Act – Voer de functie of methode uit die getest moet worden. Assert – Controleer of het resultaat overeenkomt met de verwachting.


sefa-senturk
Sefa Şentürk
Software Engineering Consultant

Als een software engineering consultant die zich richt op de backend, ben ik toegewijd aan het bouwen van robuuste, efficiënte en schaalbare systemen die uitzonderlijke gebruikerservaringen mogelijk maken. Ik ben trots op het creëren van degelijke backend architecturen, het zorgen voor naadloze integraties en het optimaliseren van prestaties om te voldoen aan de hoogste normen van betrouwbaarheid, functionaliteit en schaalbaarheid.

Ook interessant

Nieuwsgierig geworden?

Wij vertellen je graag meer!

Neem contact met ons op
Tuple Logo
Veenendaal (HQ)
De Smalle Zijde 3-05, 3903 LL Veenendaal
info@tuple.nl‭+31 318 24 01 64‬
Snel navigeren
Succesverhalen