Einleitung: Warum fortgeschrittenes Testen die Grundlage für zuverlässigen Kotlin-Code ist
In der modernen Kotlin-Entwicklung ist das Schreiben von Code nur die halbe Miete. Wahre Meisterschaft zeigt sich in der Fähigkeit, zuverlässige, testbare und leicht wartbare Systeme zu erstellen. Grundlegende Unit-Tests kennt jeder, aber wenn ein Projekt auf eine Microservice-Architektur oder komplexe Geschäftslogik anwächst, reichen einfache Überprüfungen nicht mehr aus. Fortgeschrittenes Testen umfasst nicht nur die Isolierung von Klassen, sondern auch die Überprüfung der Interaktion zwischen Komponenten (Integrationstests) und die vollständige Nachbildung von Benutzerszenarien (E2E).
Dieser Artikel richtet sich an Entwickler, die bereits mit den Grundlagen von Kotlin vertraut sind und die Qualität ihrer Projekte auf die nächste Stufe heben möchten. Wir werden drei wichtige Testebenen behandeln: Unit-, Integrations- und E2E-Tests, unter Verwendung moderner Tools: MockK, Testcontainers und Ktor Client. Sie erfahren, wie Sie Tests schreiben, die nicht nur "bestanden werden", sondern Ihren Code tatsächlich vor Regressionen schützen.
1. Unit-Tests: Isolierung und Mocks mit MockK
Unit-Tests sind das Fundament der Testpyramide. Auf fortgeschrittenem Niveau überprüfen wir nicht nur Funktionen, sondern testen komplexe Interaktionen zwischen Objekten mithilfe von Mocks. In Kotlin ist die beste Bibliothek dafür MockK.
1.1. Fortgeschrittene Funktionen von MockK
Im Gegensatz zu Mockito wurde MockK speziell für Kotlin entwickelt und unterstützt Koroutinen, Lambdas und Erweiterungsfunktionen. Betrachten wir ein Beispiel zum Testen eines Dienstes, der ein Repository und Koroutinen verwendet:
// Zu testender Dienstclass UserService(private val repo: UserRepository) { suspend fun getUserName(id: Long): String { val user = repo.findById(id) return user?.name ?: "Unknown" }}
// Testclass UserServiceTest { private val repo = mockk<UserRepository>() private val service = UserService(repo)
@Test fun `should return user name when user exists`() = runTest { // given coEvery { repo.findById(1L) } returns User(1, "Alice")
// when val result = service.getUserName(1L)
// then assertEquals("Alice", result) coVerify(exactly = 1) { repo.findById(1L) } }
@Test fun `should return Unknown when user not found`() = runTest { // given coEvery { repo.findById(any()) } returns null
// when val result = service.getUserName(99L)
// then assertEquals("Unknown", result) }}Wichtige Punkte:
coEveryundcoVerify– für die Arbeit mit suspend-Funktionen.mockk()erstellt einen leeren Mock,spyk()einen partiellen Mock eines realen Objekts.- Verwenden Sie
slotszum Erfassen von Argumenten.
1.2. Testen von Lambdas und Erweiterungsfunktionen
MockK ermöglicht das Mocken von Erweiterungsfunktionen, was für Frameworks wie Ktor oder Spring WebFlux entscheidend ist:
class StringValidator { fun String.isValidEmail(): Boolean = this.contains("@")}
// Test@Testfun `test extension mock`() { val validator = mockk<StringValidator>() with(validator) { every { "test@mail.com".isValidEmail() } returns true every { "invalid".isValidEmail() } returns false } assertTrue("test@mail.com".isValidEmail()) assertFalse("invalid".isValidEmail())}2. Integrationstests: Datenbank und Testcontainers
Integrationstests überprüfen, wie Ihr Code mit realen Abhängigkeiten interagiert: Datenbanken, Nachrichtenwarteschlangen, externen APIs. Hier kommt Testcontainers ins Spiel – eine Bibliothek, die Docker-Container direkt während der Tests hochfährt.
2.1. Einrichtung von Testcontainers mit PostgreSQL
Für eine Spring Boot-Anwendung sieht das so aus:
Blogs
Buchempfehlungen
Lesen Sie auch
Arbeit mit Datenbanken in C++: PostgreSQL und MongoDB für Anfänger
Profilierung von JavaScript-Code: Engpässe in der Praxis finden
Arbeiten mit JSON in Swift: Parsing und Serialisierung in der Praxis
PHP-Code testen: Ein Leitfaden für Anfänger von Null bis zum ersten Test
Überprüfen des Zeichenfolgenanfangs in PHP: von substr bis str_starts_with