Test-Driven Development (TDD) ist eine Entwicklungsmethodik, bei der Tests vor der Code-Implementierung geschrieben werden. Der Entwickler formuliert zunächst das erwartete Verhalten als fehlschlagenden Test, schreibt dann den minimalen Code, um ihn zu bestehen, und refaktoriert anschließend das Ergebnis. Laut Martin Fowler (2023) ist TDD keine Testtechnik – es ist eine Entwurfstechnik, die die Architektur diszipliniert und die Anzahl der Fehler in der Code-Schreibphase reduziert.
Wichtige Punkte
Test-Driven Development ist eine Softwareentwicklungspraxis, bei der automatisierte Tests das Schreiben von Produktionscode bestimmen. Im Gegensatz zum traditionellen Ansatz, bei dem Code geschrieben und dann getestet wird, kehrt TDD die Reihenfolge um: Zuerst wird der Test geschrieben, dann der Code, der den Test besteht.
Als Begründer von TDD gilt Kent Beck, der diese Praxis Ende der 1990er Jahre im Rahmen der Extreme-Programming-Methodik (XP) formulierte. In seinem Buch „Test-Driven Development: By Example“ (2002) beschrieb Beck fünf Regeln von TDD, die kanonisch wurden: Schreibe den Test vor dem Produktionscode, schreibe genau so viel Code, wie zum Bestehen des Tests nötig ist, und refaktoriere nach jedem Zyklus.
Das erste Prinzip – der Test definiert die Schnittstelle. Der Entwickler ist gezwungen, darüber nachzudenken, wie die Komponente verwendet wird, bevor er darüber nachdenkt, wie sie implementiert wird. Dies bildet von Anfang an eine saubere API.
Das zweite Prinzip – minimale Implementierung. Wenn der Test geschrieben ist, schreibt der Entwickler genau so viel Produktionscode, wie zum Bestehen benötigt wird – keine Zeile mehr. Dies verhindert vorzeitige Abstraktion und übermäßige Komplexität, die Martin Fowler als Speculative Generality bezeichnet.
Der Hauptunterschied zwischen TDD und nachträglichem Testen ist die Disziplin der Reihenfolge. Bei TDD überprüft der Test nicht nur den Code – er lenkt seine Struktur. Laut einer Microsoft-Research-Studie (Nagappan et al., 2008) zeigen Teams, die TDD anwenden, eine Reduzierung der Fehlerdichte um 40–90% im Vergleich zu Teams, die den traditionellen Ansatz verwenden.
Der Red-Green-Refactor-Zyklus ist eine dreistufige Sequenz, die für jeden neuen Test wiederholt wird. Red: Einen Test schreiben, der nicht bestanden wird. Green: Den minimalen Code schreiben, um den Test zu bestehen. Refactor: Den Code verbessern, ohne sein Verhalten zu ändern.
Der Entwickler schreibt einen Test, der eine noch nicht implementierte Funktionalität überprüft. In dieser Phase muss der Test fehlschlagen – dies bestätigt, dass der Test tatsächlich etwas überprüft. In der Android-Entwicklungsumgebung zeigt das JUnit-5-Framework für fehlgeschlagene Tests eine rote Anzeige, was der Phase ihren Namen gab.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
In dieser Phase wird der minimale Produktionscode geschrieben, der ausreicht, um den Test zu bestehen. Keine Redundanz – nur das, was für die grüne Anzeige nötig ist. Wenn die Implementierung eine Konstante sein kann, sei sie eine Konstante. Das Refactoring erfolgt im nächsten Schritt, wenn neue Tests erscheinen.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
Der grüne Test ist eine Versicherung für das Refactoring. Der Entwickler kann die Implementierung umschreiben, die Leistung optimieren oder die Lesbarkeit verbessern, in dem Vertrauen, dass der Test sofort jede Abweichung vom erwarteten Verhalten erkennt. In der Android-Entwicklung ist diese Phase besonders wichtig, um gemeinsame Schnittstellen zu extrahieren und Code-Duplizierung zu reduzieren.
Die Anwendung von TDD in mobilen Projekten bietet messbare Vorteile, die sowohl durch akademische Forschung als auch durch die Praxis führender Entwicklungsstudios bestätigt werden.
Eine IBM-Studie (Bhat & Nagappan, 2006) an vier Industrieprojekten zeigte, dass Teams, die TDD verwenden, 40% weniger Fehler produzieren im Vergleich zu ähnlichen Teams, die nach dem traditionellen Schema arbeiten. Für die mobile Entwicklung, wo die Kosten für die Behebung eines Fehlers nach der Veröffentlichung im Google Play deutlich höher sind als in der Code-Schreibphase, ist diese Metrik entscheidend.
Mit TDD geschriebene Tests dienen als lebendige Dokumentation der API. Ein Entwickler, der zum Projekt stößt, kann die Tests lesen und verstehen, wie jede Komponente verwendet werden soll. Dies ist besonders wertvoll bei hoher Teamfluktuation – einer typischen Herausforderung mobiler Studios.
Eine Codeabdeckung von über 90% ermöglicht es Entwicklern, Refactoring ohne Angst vor dem Brechen von etwas durchzuführen. Google bezeichnet in seinem Buch „Software Engineering at Google“ (2020) die Testabdeckung als Schlüsselfaktor, um die Codebasis in Projekten mit Millionen von Codezeilen sauber zu halten.
Das TDD-Ökosystem in der mobilen Entwicklung umfasst Werkzeuge für Unit-Tests, Mocking und UI-Komponentenprüfung – sowohl für Android als auch für iOS.
| Werkzeug | Plattform | Zweck |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | Basis-Framework für Unit-Tests |
| Mockito | Android | Erstellung von Mock-Objekten und Aufrufverifikation |
| MockK | Android (Kotlin) | Mocking mit Kotlin-First-Syntax und Coroutine-Unterstützung |
| Turbine | Android | Testen von Kotlin Flow und reaktiven Streams |
| XCTest | iOS (Swift) | Standard-Testframework |
Für Android-Projekte mit Kotlin umfasst der Standard-Stack JUnit 5 + MockK. MockK ist Mockito vorzuziehen, da es Kotlin-First-Funktionen – sealed classes, Coroutinen und suspend-Funktionen – ohne zusätzliche Konfiguration unterstützt.
In der iOS-Entwicklung wird TDD über XCTest implementiert – das integrierte Apple-Framework, das Assertions, Testklassen und CI/CD-Integration über Xcode Server oder GitHub Actions bietet. Für Mocking unter iOS werden die Bibliotheken Cuckoo und OHHTTPStubs verwendet.
Betrachten wir ein reales TDD-Szenario in Kotlin für Android – das Testen eines Benutzer-Repositories. Zuerst schreiben wir den Test, dann die Implementierung, die den Test besteht.
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
}
}
Nach dem Bestehen des ersten Tests fügen wir einen zweiten hinzu – wir überprüfen das Verhalten bei einem Netzwerkfehler. Nun bestimmt der Test, dass das Repository bei API-Fehler Daten aus dem Cache zurückgeben soll.
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)
}
Der Übergang zu TDD ist mit typischen Fehlern verbunden, die alle Vorteile der Methodik zunichtemachen können. Das Verständnis dieser Fallstricke hilft Teams, die Praxis effektiver einzuführen.
Das erste und häufigste Anti-Pattern ist das Testen eines zu großen Funktionsumfangs in einem einzigen Test. Ein Test sollte genau eine Behauptung überprüfen. Wenn ein Test fehlschlägt, sollte der Entwickler ohne zusätzliches Debugging genau wissen, was kaputt ist.
Der zweite Fehler ist das Schreiben eines Tests, der von Anfang an bestanden wird. Wenn der Test nicht mindestens einmal rot war, gibt es keine Gewissheit, dass er tatsächlich etwas überprüft. Regel: Vertraue niemals einem Test, den du nicht fehlschlagen gesehen hast.
Der dritte häufige Fehler ist das Anhalten in der grünen Phase. Refactoring ist kein optionaler, sondern ein obligatorischer Schritt im Zyklus. Ohne ihn degradiert die Codebasis, Tests werden spröde und die Vorteile von TDD gehen verloren.
Häufig gestellte Fragen
TDD ist in erster Linie eine Entwurfstechnik, keine Testtechnik. Tests in TDD spielen die Rolle einer Spezifikation: Sie definieren die API der Komponente vor ihrer Implementierung. Kent Beck selbst nennt TDD „eine Disziplin des Entwurfs, nicht des Testens“.
Laut Studien von Microsoft Research benötigen Teams 3 bis 6 Monate kontinuierlicher Praxis, damit TDD zur Gewohnheit wird. In den ersten 2–3 Wochen sinkt die Produktivität um 15–30%, kehrt aber nach der Anpassung auf das ursprüngliche Niveau zurück oder übertrifft es aufgrund der reduzierten Debugging-Zeit.
Ja, aber mit Einschränkungen. Für UI-Logik (ViewModel, State) ist TDD direkt anwendbar. Für visuelle Komponenten (Compose UI, SwiftUI Views) ergänzt Snapshot-Testing TDD, ersetzt es aber nicht. Es wird empfohlen, Geschäftslogik und Darstellung zu trennen.
Für Legacy-Code wird die Strategie der „Charakterisierungstests“ empfohlen – bei denen Tests auf das bestehende Verhalten geschrieben und dann der Code refaktoriert wird. Dieser Ansatz wird in Michael Feathers’ Buch „Working Effectively with Legacy Code“ (2004) beschrieben und ermöglicht die schrittweise Einführung von TDD.
TDD und Clean Architecture verstärken sich gegenseitig. Saubere Architektur erfordert klare Grenzen zwischen Schichten, und TDD zwingt den Entwickler, diese Grenzen durch Tests zu entwerfen. Die Domänenschicht wird isoliert mit Mock-Abhängigkeiten getestet, die Datenschicht durch Integrationstests.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch