Введение в тестирование в 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.
Заключение
Мы рассмотрели три уровня тестирования