Тестирование в Kotlin: unit, integration, e2e от А до Я

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

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

Начать курс

Введение в тестирование в Kotlin: от модулей до пользовательского опыта

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

1. Unit-тестирование в Kotlin: основа стабильности

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

Инструменты и настройка

Добавьте зависимости в build.gradle.kts:

dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
    testImplementation("io.mockk:mockk:1.13.8")
}

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

Допустим, у нас есть сервис, который зависит от репозитория:

class UserService(private val repo: UserRepository) {
    fun getUserName(id: Int): String {
        val user = repo.findById(id)
        return user?.name ?: "Unknown"
    }
}

Тест с использованием MockK:

import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class UserServiceTest {
    @Test
    fun `getUserName returns name when user exists`() {
        val mockRepo = mockk<UserRepository>()
        every { mockRepo.findById(1) } returns User(1, "Alice")
        val service = UserService(mockRepo)
        assertEquals("Alice", service.getUserName(1))
    }
}

Лучшие практики unit-тестов

  • Один тест — одна проверка (используйте assert с умом).
  • Изолируйте зависимости: используйте моки (MockK) или стабы.
  • Именуйте тесты понятно: shouldReturnX_whenY.
  • Тестируйте граничные случаи: null, пустые списки, исключения.

2. Integration-тестирование: проверка связки компонентов

Интеграционные тесты проверяют, как работают вместе реальные компоненты: база данных, API, файловая система. В Kotlin для этого удобно использовать Testcontainers и Ktor Client (если пишете сервер).

Пример: тестирование репозитория с PostgreSQL

Подключите Testcontainers и exposed (библиотека для работы с БД):

dependencies {
    testImplementation("org.testcontainers:postgresql:1.19.3")
    implementation("org.jetbrains.exposed:exposed:0.44.1")
}

Тест для UserRepository:

import org.junit.jupiter.api.*
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.jetbrains.exposed.sql.*

@Testcontainers
class UserRepositoryIntegrationTest {

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

    private lateinit var repo: UserRepository

    @BeforeEach
    fun setUp() {
        Database.connect(postgres.jdbcUrl, driver = "org.postgresql.Driver", user = postgres.username, password = postgres.password)
        transaction { SchemaUtils.create(Users) }
        repo = UserRepository()
    }

    @Test
    fun `save and find user by id`() {
        val user = User(1, "Bob")
        repo.save(user)
        val found = repo.findById(1)
        assertEquals("Bob", found?.name)
    }

    @AfterEach
    fun cleanUp() {
        transaction { SchemaUtils.drop(Users) }
    }
}

Советы по integration-тестам

  • Используйте контейнеры (Testcontainers) для изоляции окружения.
  • Не смешивайте unit и integration-тесты в одном классе.
  • Очищайте состояние после каждого теста (например, через @AfterEach).
  • Для HTTP-запросов используйте Ktor Mock Engine или реальный клиент с тестовым сервером.

3. E2E-тестирование: проверка системы целиком

Сквозные (end-to-end) тесты имитируют поведение реального пользователя. Они запускают всё приложение: сервер, базу данных, внешние API. Для Kotlin-серверов на Ktor или Spring Boot удобно использовать Ktor Client или Rest Assured.

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

import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

class UserE2ETest {
    @Test
    fun `full user flow - create and get`() = testApplication {
        application {
            module() // ваш модуль с маршрутами
        }
        val client = createClient {
            // настройки клиента
        }
        // Шаг 1: создать пользователя
        val createResponse = client.post("/users") {
            setBody("""{"name":"Charlie"}""")
            contentType(io.ktor.http.ContentType.Application.Json)
        }
        assertEquals(201, createResponse.status.value)

        // Шаг 2: получить пользователя
        val getResponse = client.get("/users/1")
        val body = getResponse.bodyAsText()
        assertEquals(200, getResponse.status.value)
        assert(body.contains("Charlie"))
    }
}

Особенности e2e-тестов

  • Они медленные — запускайте их отдельно от unit и integration (например, @Tag("e2e")).
  • Используйте тестовые базы данных (H2 в памяти или Testcontainers).
  • Проверяйте полные сценарии: регистрация → авторизация → CRUD.
  • Для Android e2e используйте Espresso или Compose Test.

Заключение

Мы рассмотрели три уровня тестирования

Блоги

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