Тестирование в Kotlin: unit, integration, e2e — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в тестирование в Kotlin: от юнит-тестов до end-to-end

Тестирование — неотъемлемая часть разработки надёжного программного обеспечения. В экосистеме Kotlin, который активно используется для Android-разработки, бэкенда на Ktor и многоплатформенных проектов, существует мощный инструментарий для всех уровней тестирования. В этой статье мы разберём три ключевых уровня: unit-тесты, интеграционные тесты и end-to-end (e2e) тесты. Вы узнаете, чем они отличаются, когда их применять и как писать эффективные тесты на Kotlin с примерами кода.

Unit-тестирование в Kotlin: основа надёжности

Unit-тесты проверяют работу отдельных компонентов (функций, классов) в изоляции. В Kotlin стандартом де-факто является фреймворк JUnit 5 в связке с библиотекой моков MockK.

Пример unit-теста с JUnit 5 и MockK

Рассмотрим простой сервис для расчёта скидки:

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
    }
}

Тест для этого сервиса:

import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import 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)
    }
}

Ключевые моменты:

  • Используйте mockk{} для создания моков.
  • Блок every { ... } returns ... задаёт поведение мока.
  • Структура Arrange-Act-Assert делает тесты читаемыми.
  • Unit-тесты должны выполняться быстро и не зависеть от внешних ресурсов.

Интеграционное тестирование: проверка взаимодействия компонентов

Интеграционные тесты проверяют, как несколько модулей работают вместе — например, сервис с реальной базой данных или внешним API. Для Kotlin популярны Testcontainers (для Docker-контейнеров) и Spring Boot Test (для бэкенда).

Пример интеграционного теста с Testcontainers и Ktor

Предположим, у нас есть Ktor-приложение с 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"))
        }
    }
}

Интеграционный тест с Testcontainers:

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
class UserRepositoryIntegrationTest : StringSpec({

    @Container
    val postgres = PostgreSQLContainer<Nothing>("postgres:15").apply {
        withDatabaseName("testdb")
        withUsername("test")
        withPassword("test")
    }

    "should find user by id" {
        // Реальная инициализация подключения и создание таблицы
        val database = createDatabase(postgres.jdbcUrl, postgres.username, postgres.password)
        val repo = UserRepository(database)

        val user = repo.findById("123")
        user shouldBe User("123", true)
    }
})

Рекомендации:

  • Используйте Testcontainers для изоляции внешних зависимостей.
  • Не тестируйте всю систему сразу — фокусируйтесь на границах модулей.
  • Очищайте тестовые данные после каждого теста.

End-to-End (e2e) тестирование: проверка системы целиком

E2e-тесты симулируют поведение реального пользователя, проходя через все слои приложения: UI → API → база данных → внешние сервисы. Для Kotlin часто используют Selenium (веб), Espresso (Android) или Ktor Client для API.

Пример e2e-теста для REST API на Ktor

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable
import kotlin.test.Test
import kotlin.test.assertEquals

@Serializable
data class OrderResponse(val id: String, val status: String)

class OrderE2ETest {

    private val client = HttpClient {
        install(ContentNegotiation) { json() }
    }

    @Test
    suspend fun `should create order successfully`() {
        // 1. Регистрация пользователя
        val token = registerUser(client)
        
        // 2. Создание заказа
        val response: OrderResponse = client.post("https://api.example.com/orders") {
            header(HttpHeaders.Authorization, "Bearer $token")
            setBody(mapOf("productId" to "prod-1", "quantity" to 2))
        }.body()

        // 3. Проверка статуса
        assertEquals("pending", response.status)

        // 4. Проверка через GET-запрос
        val order: OrderResponse = client.get("https://api.example.com/orders/${response.id}") {
            header(HttpHeaders.Authorization, "Bearer $token")
        }.body()
        assertEquals("pending", order.status)
    }
}

Важные принципы e2e-тестов:

  • Тестируйте критические пользовательские сценарии (логин, покупка, регистрация).
  • Используйте отдельную тестовую среду (staging) — не трогайте продакшн.
  • E2e-тесты медленные, поэтому их количество должно быть ограничено.
  • Добавьте retry-

Блоги

Рекомендаций по книгам