E2E тестирование Kotlin приложений: от нуля до production

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

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

Начать курс

Почему E2E тестирование — ваш лучший друг (и как подружиться с ним в Kotlin)

Представьте: вы написали крутой сервис на Kotlin, всё скомпилировалось, модульные тесты зелёные, код-ревью прошло. Вы выкатываете в прод, и... пользователи жалуются, что кнопка «Купить» не работает, хотя юнит-тесты её проверяли. Знакомо? Добро пожаловать в мир, где E2E тестирование Kotlin приложений — не роскошь, а необходимость.

Сквозное (end-to-end) тестирование — это проверка всего пользовательского пути: от клика по кнопке до записи в базу данных и обратно. В этой статье мы не просто поговорим о теории. Мы напишем реальные тесты для Kotlin-приложения, разберём инструменты и покажем, как не сойти с ума при настройке инфраструктуры. Пристегнитесь, будет код и кофе.

Что такое E2E тестирование и чем оно отличается от других видов

Давайте сразу расставим точки над i. E2E (или сквозное) тестирование проверяет систему целиком, как её видит пользователь. В отличие от:

  • Юнит-тестов — проверяют один метод/класс изолированно.
  • Интеграционных тестов — проверяют взаимодействие двух-трёх компонентов (например, контроллер + сервис).
  • UI-тестов — часто путают с E2E, но UI-тест может не проверять бэкенд, а только отрисовку интерфейса.

E2E тестирование Kotlin приложений — это полный цикл: пользовательский интерфейс (если есть), API, бизнес-логика, база данных, внешние сервисы. Всё должно работать вместе как часы.

Инструменты для E2E тестирования в Kotlin

Выбор инструмента зависит от того, что вы тестируете: Android-приложение, веб-сервис на Ktor или десктопное приложение. Вот топ для Kotlin-разработчика:

  • Selenide + Kotlin — для веб-интерфейсов. Обёртка над Selenium, но с лаконичным синтаксисом, идеально подходит для Kotlin.
  • Ktor Client + TestContainers — для бэкенда. Поднимаете тестовую инфраструктуру (БД, кеш) в Docker и гоняете HTTP-запросы.
  • Espresso + Compose Test — для Android. Нативные инструменты, которые поставляются с Android SDK.
  • Cucumber + Kotlin — если нужны BDD-сценарии на понятном языке для заказчика.

Мы сосредоточимся на двух первых сценариях, так как они наиболее универсальны.

Пишем E2E тест для веб-приложения на Kotlin (Selenide)

Допустим, у нас есть простой интернет-магазин на Spring Boot + Kotlin. Нам нужно проверить сценарий: пользователь заходит на сайт, добавляет товар в корзину и оформляет заказ.

Сначала добавим зависимости в build.gradle.kts:

dependencies {
    testImplementation("com.codeborne:selenide:6.19.1")
    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
    testImplementation("io.github.bonigarcia:webdrivermanager:5.6.2")
}

Теперь напишем сам тест. Обратите внимание: мы используем extension-функции Kotlin, чтобы сделать код читаемым:

import com.codeborne.selenide.Condition.visible
import com.codeborne.selenide.Selenide.`$`
import com.codeborne.selenide.Selenide.open
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class ShopE2ETest {

    @BeforeEach
    fun setUp() {
        // Открываем браузер перед каждым тестом
        open("https://test-shop.example.com")
    }

    @Test
    fun `user can add product to cart and checkout`() {
        // Шаг 1: Найти товар и добавить в корзину
        `$`("[data-testid='product-1']").shouldBe(visible).click()
        `$`("[data-testid='add-to-cart']").click()

        // Шаг 2: Проверить, что корзина не пуста
        `$`("[data-testid='cart-badge']").shouldHave(text("1"))

        // Шаг 3: Перейти к оформлению
        `$`("[data-testid='checkout-btn']").click()

        // Шаг 4: Заполнить форму
        `$`("[name='email']").setValue("test@example.com")
        `$`("[name='address']").setValue("ул. Программистов, д. 42")
        `$`("[data-testid='submit-order']").click()

        // Шаг 5: Проверить успешное оформление
        `$`("[data-testid='order-success']").shouldBe(visible)
    }
}

Важно: Используйте data-testid атрибуты, а не CSS-классы или XPath. Это делает тесты устойчивыми к изменениям вёрстки.

E2E тестирование бэкенда на Ktor с TestContainers

Теперь представим, что у нас микросервис на Ktor, который общается с PostgreSQL. Нам нужно проверить полный цикл: POST запрос → сохранение в БД → GET запрос возвращает данные.

Добавим зависимости:

dependencies {
    testImplementation("io.ktor:ktor-server-test-host:2.3.7")
    testImplementation("org.testcontainers:postgresql:1.19.3")
    testImplementation("org.jetbrains.exposed:exposed:0.44.1")
}

Вот как выглядит E2E тест для такого сценария:

import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.server.testing.*
import org.junit.jupiter.api.Test
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
class UserServiceE2ETest {

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

    @Test
    fun `create user and retrieve it via API`() = testApplication {
        // Настраиваем приложение с тестовой БД
        environment {
            config = MapApplicationConfig(
                "db.url" to postgres.jdbcUrl,
                "db.user" to postgres.username,
                "db.password" to postgres.password
            )
        }
        application {
            module() // ваш модуль приложения
        }

        // Шаг 1: Создаём пользователя
        val createResponse = client.post("/api/users") {
            contentType(ContentType.Application.Json)
            setBody("""{"name": "Иван Петров", "email": "ivan@test.com"}""")
        }
        assert(createResponse.status == HttpStatusCode.Created)

        // Шаг 2: Получаем список пользователей
        val getResponse = client.get("/api/users")
        val body = getResponse.bodyAsText()

        // Шаг 3: Проверяем, что наш пользователь там есть
        assert(body.contains("Иван Петров"))
        assert(body.contains("ivan@test.com"))
    }
}

Обратите внимание: TestCont

Блоги

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