BDD на Kotlin: Как писать тесты, которые поймут даже менеджеры

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

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

Начать курс

BDD на Kotlin: Пишем тесты, которые читают и менеджеры, и разработчики

Вы когда-нибудь показывали unit-тест своему продакт-менеджеру? Скорее всего, нет. Потому что это всё равно что показывать чертежи реактивного двигателя пилоту — полезно, но непонятно. Behaviour-Driven Development (BDD) на Kotlin решает именно эту проблему: он превращает сухие assert'ы в живые сценарии, которые описывают поведение системы человеческим языком.

В этой статье мы разберём, как внедрить BDD на Kotlin с помощью современных фреймворков — Kotest и Spek. Вы увидите, как писать тесты в стиле Given-When-Then, которые одновременно являются и документацией, и автоматической проверкой. И да, их сможет прочитать даже ваш тимлид без подготовки.

Что такое BDD и чем он отличается от TDD?

Многие путают TDD (Test-Driven Development) и BDD (Behaviour-Driven Development). Разница проста:

  • TDD — это про «как я проверю этот метод?»
  • BDD — это про «как пользователь получит ценность от этой функции?»

Behaviour-driven development фокусируется на поведении системы, а не на деталях реализации. Вы описываете сценарий: при определённых условиях (Given), когда происходит действие (When), система выдаёт ожидаемый результат (Then).

Например, вместо того чтобы писать:
assertEquals(42, calculator.add(20, 22)),
вы напишете:

Given("у меня есть калькулятор") {
    val calc = Calculator()
    When("я складываю 20 и 22") {
        val result = calc.add(20, 22)
        Then("результат должен быть 42") {
            result shouldBe 42
        }
    }
}

Согласитесь, второй вариант читается как история, а не как техническая инструкция.

Почему Kotlin идеален для BDD

Kotlin — язык, который сам по себе располагает к написанию читаемого кода. Функции-расширения, инфиксная нотация, лямбды с ресивером — всё это делает BDD тесты на Kotlin максимально естественными. Вы можете писать shouldBe, shouldThrow и другие матчеры, которые выглядят как английский язык.

Кроме того, Kotlin поддерживает несколько парадигм, что позволяет использовать разные фреймворки для BDD. Рассмотрим два самых популярных.

Фреймворк Kotest: BDD из коробки

Kotest (ранее KotlinTest) — это мощный мультиплатформенный тестовый фреймворк. Он поддерживает несколько стилей, включая BehaviorSpec, который идеально подходит для BDD.

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

import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe

class CartSpec : BehaviorSpec({
    given("пустая корзина") {
        val cart = ShoppingCart()
        `when`("добавляем товар с ценой 100") {
            cart.addItem(Item("Книга", 100.0))
            then("в корзине должен быть 1 товар") {
                cart.itemCount shouldBe 1
            }
            then("общая сумма должна быть 100") {
                cart.total shouldBe 100.0
            }
        }
        `when`("добавляем товар и применяем скидку 10%") {
            cart.addItem(Item("Книга", 100.0))
            cart.applyDiscount(10.0)
            then("итоговая сумма должна быть 90") {
                cart.total shouldBe 90.0
            }
        }
    }
})

Обратите внимание на обратные кавычки вокруг when. Это особенность Kotlin: when — зарезервированное слово, но в кавычках оно работает как идентификатор. Некоторые разработчики предпочитают использовать When с заглавной буквы.

Spek: структурированный BDD для Kotlin

Spek — ещё один популярный фреймворк для behaviour-driven development Kotlin. Он вдохновлён RSpec из Ruby и использует вложенные группы describe, context, it.

import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
import kotlin.test.assertEquals

class UserRegistrationSpec : Spek({
    describe("регистрация пользователя") {
        val validator = EmailValidator()
        context("когда email валидный") {
            it("должен вернуть true") {
                assertEquals(true, validator.isValid("test@example.com"))
            }
        }
        context("когда email без @") {
            it("должен вернуть false") {
                assertEquals(false, validator.isValid("testexample.com"))
            }
        }
        context("когда email пустой") {
            it("должен выбросить исключение") {
                assertThrows<IllegalArgumentException> {
                    validator.isValid("")
                }
            }
        }
    }
})

Spek отлично подходит для проектов, где важна строгая структура и чёткое разделение контекстов. Он также поддерживает параллельное выполнение тестов.

Given-When-Then: шаблон, который спасает проекты

Шаблон Given-When-Then — это сердце BDD. Давайте разберём его на реальном примере из интернет-магазина.

  • Given — начальное состояние системы (предусловия)
  • When — действие, которое совершает пользователь
  • Then — ожидаемый результат

Вот пример для сервиса заказов:

class OrderServiceSpec : BehaviorSpec({
    given("пользователь с балансом 500 рублей") {
        val user = User(balance = 500.0)
        val service = OrderService()
        `when`("он оформляет заказ на 300 рублей") {
            val order = service.placeOrder(user, listOf(Item("Товар", 300.0)))
            then("заказ должен быть подтверждён") {
                order.status shouldBe OrderStatus.CONFIRMED
            }
            then("баланс пользователя должен стать 200") {
                user.balance shouldBe 200.0
            }
        }
    }
})

Такой тест легко читать не только разработчикам, но и тестировщикам, аналитикам и даже менеджерам. Это снижает количество недопониманий и багов на стыке бизнеса и разработки.

Интеграция BDD с Cucumber на Kotlin

Если вы работаете в команде, где сценарии пишутся на Gherkin (файлы .feature), вы можете использовать Cucumber для Kotlin. Это позволяет хранить сценарии в едином формате, понятном всем участникам проекта.

Пример файла checkout.feature:

Feature: Оформление заказа
  Scenario: Успешная покупка
    Given пользователь авторизован
    When он добавляет товар в корзину
    And нажимает "Оформить заказ"
    Then заказ создаётся с стату                

Блоги

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