Einführung in das Testen in Kotlin: von Unit-Tests bis End-to-End
Testen ist ein wesentlicher Bestandteil der Entwicklung zuverlässiger Software. Im Kotlin-Ökosystem, das aktiv für die Android-Entwicklung, Backend mit Ktor und plattformübergreifende Projekte genutzt wird, gibt es leistungsstarke Werkzeuge für alle Testebenen. In diesem Artikel betrachten wir drei Schlüsselebenen: Unit-Tests, Integrationstests und End-to-End (E2E) Tests. Sie erfahren, wie sie sich unterscheiden, wann sie anzuwenden sind und wie man effektive Tests in Kotlin mit Codebeispielen schreibt.
Unit-Testing in Kotlin: Grundlage der Zuverlässigkeit
Unit-Tests überprüfen die Arbeit einzelner Komponenten (Funktionen, Klassen) in Isolation. In Kotlin ist der De-facto-Standard das Framework JUnit 5 in Verbindung mit der Mock-Bibliothek MockK.
Beispiel eines Unit-Tests mit JUnit 5 und MockK
Betrachten wir einen einfachen Service zur Berechnung eines Rabatts:
class DiscountService(private val userRepository: UserRepository) { fun calculateDiscount(userId: String, amount: Double): Double { val user = userRepository.findById(userId) ?: return amount return if (user.isPremium) amount * 0.9 else amount }}Test für diesen Service:
import io.mockk.everyimport io.mockk.mockkimport org.junit.jupiter.api.Testimport kotlin.test.assertEquals
class DiscountServiceTest {
@Test fun `should apply 10% discount for premium user`() { // Arrange val userRepo = mockk<UserRepository>() every { userRepo.findById("123") } returns User("123", isPremium = true) val service = DiscountService(userRepo)
// Act val result = service.calculateDiscount("123", 100.0)
// Assert assertEquals(90.0, result) }
@Test fun `should not apply discount for regular user`() { val userRepo = mockk<UserRepository>() every { userRepo.findById("456") } returns User("456", isPremium = false) val service = DiscountService(userRepo)
val result = service.calculateDiscount("456", 100.0)
assertEquals(100.0, result) }}Wichtige Punkte:
- Verwenden Sie
mockk{}zum Erstellen von Mocks. - Der Block
every { ... } returns ...definiert das Verhalten des Mocks. - Die Struktur Arrange-Act-Assert macht Tests lesbar.
- Unit-Tests sollten schnell ausgeführt werden und nicht von externen Ressourcen abhängen.
Integrationstesting: Überprüfung der Komponenteninteraktion
Integrationstests überprüfen, wie mehrere Module zusammenarbeiten – zum Beispiel ein Service mit einer echten Datenbank oder einer externen API. Für Kotlin sind Testcontainers (für Docker-Container) und Spring Boot Test (für Backend) beliebt.
Beispiel eines Integrationstests mit Testcontainers und Ktor
Angenommen, wir haben eine Ktor-Anwendung mit PostgreSQL:
class UserRepository(private val database: Database) { suspend fun findById(id: String): User? { return database.query("SELECT * FROM users WHERE id = ?", id).singleOrNull { row -> User(row.string("id"), row.boolean("is_premium")) } }}Integrationstest mit Testcontainers:
import io.kotest.core.spec.style.StringSpecimport io.kotest.matchers.shouldBeimport org.testcontainers.containers.PostgreSQLContainerimport org.testcontainers.junit.jupiter.Containerimport org.testcontainers.junit.jupiter.Testcontainers
@Testcontainersclass UserRepositoryIntegrationTest : StringSpec({
@Container val postgres = PostgreSQLContainer<Nothing>("postgres:15").apply { withDatabaseName("testdb") withUsername("test") withPassword("test") }
"should