Introduction aux tests en Kotlin : des tests unitaires aux tests end-to-end
Les tests sont une partie essentielle du développement de logiciels fiables. Dans l'écosystème Kotlin, qui est activement utilisé pour le développement Android, le backend avec Ktor et les projets multiplateformes, il existe une boîte à outils puissante pour tous les niveaux de test. Dans cet article, nous aborderons trois niveaux clés : les tests unitaires, les tests d'intégration et les tests end-to-end (e2e). Vous apprendrez en quoi ils diffèrent, quand les utiliser et comment écrire des tests efficaces en Kotlin avec des exemples de code.
Tests unitaires en Kotlin : la base de la fiabilité
Les tests unitaires vérifient le fonctionnement de composants individuels (fonctions, classes) en isolation. En Kotlin, le standard de facto est le framework JUnit 5 associé à la bibliothèque de mocks MockK.
Exemple de test unitaire avec JUnit 5 et MockK
Considérons un service simple pour calculer une remise :
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 pour ce 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) }}Points clés :
- Utilisez
mockk{}pour créer des mocks. - Le bloc
every { ... } returns ...définit le comportement du mock. - La structure Arrange-Act-Assert rend les tests lisibles.
- Les tests unitaires doivent s'exécuter rapidement et ne pas dépendre de ressources externes.
Tests d'intégration : vérification de l'interaction des composants
Les tests d'intégration vérifient comment plusieurs modules fonctionnent ensemble — par exemple, un service avec une base de données réelle ou une API externe. Pour Kotlin, Testcontainers (pour les conteneurs Docker) et Spring Boot Test (pour le backend) sont populaires.
Exemple de test d'intégration avec Testcontainers et Ktor
Supposons que nous ayons une application Ktor avec 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")) } }}Test d'intégration avec 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