Test-Driven Development (TDD) — is een ontwikkelingsmethodologie waarbij tests worden geschreven vóór de implementatie van code. De ontwikkelaar formuleert eerst het verwachte gedrag in de vorm van een falende test, schrijft vervolgens minimale code om deze te laten slagen en refactort daarna het resultaat. Volgens Martin Fowler (2023) is TDD geen testtechniek — het is een ontwerptechniek die de architectuur disciplineert en het aantal defecten in de schrijffase van code vermindert.
Belangrijkste
Test-Driven Development — is een softwareontwikkelingspraktijk waarbij geautomatiseerde tests het schrijven van productiecode bepalen. In tegenstelling tot de traditionele benadering, waarbij code wordt geschreven en vervolgens getest, keert TDD de volgorde om: eerst wordt de test geschreven, dan de code die deze test doorstaat.
De grondlegger van TDD is Kent Beck, die deze praktijk aan het einde van de jaren 1990 formuleerde binnen de methodologie Extreme Programming (XP). In het boek „Test-Driven Development: By Example" (2002) beschreef Beck vijf regels van TDD die canoniek zijn geworden: schrijf de test vóór de productiecode, schrijf precies zoveel code als nodig is om de test te laten slagen en refactor na elke cyclus.
Het eerste principe — de test bepaalt de interface. De ontwikkelaar wordt gedwongen na te denken over hoe het component zal worden gebruikt voordat hij nadenkt over hoe het is geïmplementeerd. Dit vormt vanaf het begin een schone API.
Het tweede principe — minimale implementatie. Wanneer de test is geschreven, schrijft de ontwikkelaar precies zoveel productiecode als nodig is om hem te laten slagen — geen regel meer. Dit voorkomt voortijdige abstractie en overmatige complexiteit, die Martin Fowler Speculative Generality noemt.
Het belangrijkste verschil tussen TDD en testen „achteraf" — volgordediscipline. In TDD controleert de test niet alleen de code — hij stuurt de structuur ervan. Volgens onderzoek van Microsoft Research (Nagappan et al., 2008) vertonen teams die TDD toepassen een vermindering van de defectdichtheid met 40–90% in vergelijking met teams die de traditionele benadering gebruiken.
De Red-Green-Refactor-cyclus — is een driestapsreeks die voor elke nieuwe test wordt herhaald. Red: schrijf een test die niet slaagt. Green: schrijf minimale code om de test te laten slagen. Refactor: verbeter de code zonder het gedrag te veranderen.
De ontwikkelaar schrijft een test die nog niet geïmplementeerde functionaliteit controleert. In deze fase moet de test falen — dit bevestigt dat de test echt iets controleert. In de Android-ontwikkelomgeving toont het JUnit 5-framework een rode indicatie voor falende tests, wat de fase zijn naam heeft gegeven.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
In deze fase wordt minimale productiecode geschreven die voldoende is om de test te laten slagen. Geen redundantie — alleen wat nodig is voor de groene indicatie. Als de implementatie een constante kan zijn — laat het een constante zijn. Refactoring zal in de volgende stap plaatsvinden wanneer nieuwe tests verschijnen.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
De groene test is een verzekering voor refactoring. De ontwikkelaar kan de implementatie herschrijven, de prestaties optimaliseren of de leesbaarheid verbeteren, in de wetenschap dat de test onmiddellijk elke afwijking van het verwachte gedrag zal detecteren. In mobiele ontwikkeling op Android is deze fase vooral belangrijk voor het scheiden van gemeenschappelijke interfaces en het verminderen van codeduplicatie.
Het toepassen van TDD in mobiele projecten levert meetbare voordelen op, bevestigd door zowel academisch onderzoek als de praktijk van toonaangevende ontwikkelingsstudio's.
Onderzoek van IBM (Bhat & Nagappan, 2006) op vier industriële projecten toonde aan dat teams die TDD gebruiken 40% minder defecten maken in vergelijking met vergelijkbare teams die traditioneel werken. Voor mobiele ontwikkeling, waar de kosten van het repareren van een bug na release in Google Play aanzienlijk hoger zijn dan in de schrijffase van code, is deze metric cruciaal.
Tests geschreven volgens TDD dienen als levende documentatie van de API. Een ontwikkelaar die in het project komt, kan de tests lezen en begrijpen hoe elk component moet worden gebruikt. Dit is vooral waardevol in situaties van hoge teamverloop — een typisch probleem van mobiele studio's.
Code dekking door tests die 90% overschrijdt, stelt ontwikkelaars in staat om refactoring uit te voeren zonder angst om iets te breken. Google noemt in zijn boek „Software Engineering at Google" (2020) testdekking de belangrijkste factor die het mogelijk maakt de codebase schoon te houden in projecten met miljoenen regels code.
Het TDD-ecosysteem in mobiele ontwikkeling omvat tools voor unittesten, mocking en controle van UI-componenten — zowel voor Android als iOS.
| Tool | Platform | Doel |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | Basis framework voor unittesten |
| Mockito | Android | Maken van mock-objecten en verificatie van aanroepen |
| MockK | Android (Kotlin) | Mocking met Kotlin-first syntaxis en ondersteuning voor coroutines |
| Turbine | Android | Testen van Kotlin Flow en reactieve stromen |
| XCTest | iOS (Swift) | Standaard testframework |
Voor Android-projecten in Kotlin omvat de standaard stack JUnit 5 + MockK. MockK heeft de voorkeur boven Mockito omdat het Kotlin first-class functions ondersteunt — sealed class, coroutines en suspend-functies zonder extra configuratie.
In iOS-ontwikkeling wordt TDD geïmplementeerd via XCTest — het ingebouwde Apple-framework dat assertions, testklassen en integratie met CI/CD via Xcode Server of GitHub Actions biedt. Voor mocking op iOS worden de bibliotheken Cuckoo en OHHTTPStubs gebruikt.
Laten we een realistisch TDD-scenario in Kotlin voor Android bekijken — het testen van een gebruikersrepository. Eerst schrijven we de test, dan — de implementatie die deze test doorstaat.
class UserRepositoryTest {
private val api = mockk<UserApi>()
private val dao = mockk<UserDao>()
private val repo = UserRepository(api, dao)
fun `when api returns user then cache and emit`() = runTest {
val user = User(1, "Alice")
coEvery { api.getUser(1) } returns user
every { dao.insert(user) } returns Unit
val result = repo.getUser(1)
assertEquals(user, result)
verify { dao.insert(user) }
}
}
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUser(id: Int): User {
val user = api.getUser(id)
dao.insert(user)
return user
}
}
Na het doorstaan van de eerste test voegen we een tweede toe — we controleren het gedrag bij een netwerkfout. Nu bepaalt de test dat bij een API-storing de repository gegevens uit de cache moet retourneren.
fun `when api fails then return cached user`() = runTest {
val cached = User(1, "Cached Alice")
coEvery { api.getUser(1) } throws IOException()
every { dao.getById(1) } returns cached
val result = repo.getUser(1)
assertEquals(cached, result)
}
De overgang naar TDD gaat gepaard met typische fouten die alle voordelen van de methodologie teniet kunnen doen. Inzicht in deze valkuilen helpt teams de praktijk effectiever te implementeren.
Het eerste en meest voorkomende anti-patroon — het testen van een te grote hoeveelheid functionaliteit in één test. Een test moet precies één bewering controleren (één assertion). Als een test faalt, moet de ontwikkelaar weten wat er precies is gebroken zonder extra debugging.
De tweede fout — het schrijven van een test die vanaf het begin slaagt. Als een test niet minstens één keer rood is geweest, is er geen zekerheid dat hij überhaupt iets controleert. Regel: vertrouw nooit een test die je niet hebt zien falen.
De derde typische fout — stoppen in de groene fase. Refactoring is geen optionele, maar een verplichte fase van de cyclus. Zonder dit degradeert de codebase, worden tests breekbaar en gaan de voordelen van TDD verloren.
Veelgestelde vragen
TDD is in de eerste plaats een ontwerptechniek, geen testtechniek. Tests in TDD spelen de rol van specificatie: ze bepalen de API van een component vóór de implementatie. Kent Beck zelf noemt TDD „een ontwerpdiscipline, geen testdiscipline".
Volgens onderzoek van Microsoft Research hebben teams 3 tot 6 maanden ononderbroken oefening nodig om TDD een gewoonte te laten worden. De eerste 2–3 weken daalt de productiviteit met 15–30%, maar na aanpassing keert deze terug naar het oorspronkelijke niveau of overtreft het dankzij de vermindering van debugtijd.
Ja, maar met beperkingen. Voor UI-logica (ViewModel, State) is TDD direct toepasbaar. Voor visuele componenten (Compose UI, SwiftUI Views) vult snapshot testing TDD aan, maar vervangt het niet. Het wordt aanbevolen om bedrijfslogica en presentatie te scheiden.
Voor legacy-code wordt de strategie van karakterisatietests (characterization tests) aanbevolen — waarbij tests worden geschreven op bestaand gedrag en vervolgens de code wordt gerefactord. Deze aanpak, beschreven in het boek van Michael Feathers „Working Effectively with Legacy Code" (2004), maakt gefaseerde implementatie van TDD mogelijk.
TDD en Clean Architecture versterken elkaar wederzijds. Schone architectuur vereist duidelijke grenzen tussen lagen, en TDD dwingt de ontwikkelaar om deze grenzen te ontwerpen via tests. De domeinlaag wordt geïsoleerd getest met mock-afhankelijkheden, de gegevenslaag — via integratietests.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook