Тестирование в Kotlin: Unit, Integration, E2E — полное руководство

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

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

Начать курс

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

Качественная кодовая база невозможна без автоматизированного тестирования. Kotlin, как современный язык для JVM, предоставляет разработчикам богатый инструментарий для создания надежных тестов. В этой статье мы разберем три уровня пирамиды тестирования применительно к Kotlin: unit (модульные), integration (интеграционные) и e2e (сквозные). Вы узнаете, как писать эффективные тесты на Kotlin с использованием популярных фреймворков, таких как JUnit 5, MockK, Ktor Client и Selenium.

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

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

1.1. Настройка проекта

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

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

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

Предположим, у нас есть сервис UserService, который зависит от репозитория:

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

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

import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals

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

        val result = service.getUserName(1L)

        assertEquals("Alice", result)
    }
}

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

  • Изолируйте зависимости — используйте моки для всех внешних вызовов (базы данных, API).
  • Тестируйте граничные случаи: null, пустые строки, отрицательные числа.
  • Используйте понятные имена тестов — они должны описывать сценарий и ожидаемый результат.
  • Следуйте принципу AAA: Arrange (подготовка), Act (действие), Assert (проверка).

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

Интеграционные тесты проверяют, как несколько компонентов системы работают вместе. В Kotlin это может быть тестирование REST API с использованием Ktor Client или проверка работы с базой данных через Exposed.

2.1. Тестирование REST API с Ktor

Создаем простой сервер на Ktor и тестируем его:

// Сервер
fun Application.module() {
    routing {
        get("/hello") {
            call.respondText("Hello, Kotlin!")
        }
    }
}

// Тест
class ApiTest {
    @Test
    fun `test hello endpoint`() = testApplication {
        application { module() }
        val response = client.get("/hello")
        assertEquals(HttpStatusCode.OK, response.status)
        assertEquals("Hello, Kotlin!", response.bodyAsText())
    }
}

2.2. Работа с тестовой базой данных

Для интеграционных тестов с БД используйте H2 (в памяти) или Testcontainers для PostgreSQL:

@Test
fun `test user creation`() {
    withTestDatabase {
        val user = User.new {
            name = "Bob"
            email = "bob@test.com"
        }
        assertEquals("Bob", user.name)
        assertNotNull(user.id)
    }
}

private fun withTestDatabase(block: () -> Unit) {
    Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
    transaction {
        SchemaUtils.create(User)
        block()
    }
}

2.3. Советы по интеграционным тестам

  • Используйте in-memory базы для быстрых тестов, но учитывайте различия с продакшеном.
  • Очищайте данные между тестами — используйте аннотацию @BeforeEach или транзакционные коллбэки.
  • Тестируйте только один сценарий на тест-метод.

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

End-to-end (E2E) тесты имитируют действия реального пользователя, проверяя работу приложения целиком — от интерфейса до базы данных. Для Kotlin-приложений часто используют Selenium WebDriver или Playwright.

3.1. Пример E2E теста с Selenium

class LoginE2ETest {
    private lateinit var driver: WebDriver

    @BeforeEach
    fun setup() {
        driver = ChromeDriver()
    }

    @Test
    fun `user can login with valid credentials`() {
        driver.get("http://localhost:8080/login")
        driver.findElement(By.name("username")).sendKeys("admin")
        driver.findElement(By.name("password")).sendKeys("password123")
        driver.findElement(By.id("login-btn")).click()

        val welcomeText = driver.findElement(By.id("welcome")).text
        assertEquals("Welcome, admin!", welcomeText)
    }

    @AfterEach
    fun teardown() {
        driver.quit()
    }
}

2.2. Запуск E2E тестов в CI/CD

Для автоматизации E2E тестов используйте:

  • Testcontainers — для поднятия тестовых контейнеров (база данных, брокер сообщений).
  • Gradle — настройте отдельную задачу для E2E:
tasks.register<Test>("e2eTest") {
    useJUnitPlatform {
        includeTags("e2e")
    }
}

3.3. Важные принципы E2E

  • Минимизируйте количество E2E тестов — они медленные и хрупкие.
  • Тестируйте критически важные сценарии: регистрация, оформление заказа, вход.
  • Используйте Page Object паттерн для организации к

Блоги

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