BDD на Rust: Пишем живой код с Cucumber и Specs

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

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

Начать курс

Почему BDD на Rust — это не про скучные тесты

Представьте: вы пишете на Rust высоконагруженный сервис. Код безопасный, быстрый, но когда продукт приходит к заказчику, он говорит: «А я думал, кнопка должна работать иначе». Знакомо? Классический разрыв между кодом и ожиданиями. Именно здесь на помощь приходит BDD (Behaviour-Driven Development) — методология, которая переводит бизнес-требования на язык, понятный и менеджеру, и компилятору Rust.

В этой статье мы разберём, как внедрить BDD на Rust на практике. Вы узнаете, как писать сценарии на человеческом языке (Gherkin), автоматизировать их с помощью cucumber и specs, и почему это делает ваш код не только надёжным, но и прозрачным для всей команды. Никакой воды — только живой код и реальные примеры.

Что такое BDD и почему Rust для него идеален

Behaviour-Driven Development — это эволюция TDD. Если TDD фокусируется на «как тестировать код», то BDD отвечает на вопрос «какое поведение мы хотим видеть». Вы пишете сценарии на языке Gherkin (Given-When-Then), которые одновременно являются и тестами, и документацией.

Rust — язык, который обожает строгость и предсказуемость. BDD в Rust даёт:

  • Самодокументируемый код — любой разработчик читает сценарий и понимает бизнес-логику.
  • Безопасность на уровне типов — компилятор проверяет, что ваши шаги соответствуют структуре.
  • Параллельный запуск — тесты BDD в Rust работают быстро благодаря async и многопоточности.

Когда BDD на Rust особенно полезен?

  • API-сервисы с чёткими контрактами (REST, GraphQL).
  • Системы с бизнес-правилами (финансы, логистика, игры).
  • Микросервисы, где важна интеграция между компонентами.

Инструменты для BDD в Rust: Cucumber и Specs

Экосистема Rust предлагает два основных подхода:

1. cucumber — классический BDD-фреймворк

Позволяет писать сценарии на Gherkin (файлы .feature) и связывать их с Rust-кодом. Поддерживает async, кастомные макросы и отчёты.

2. specs — лёгкий и быстрый аналог

Не требует Gherkin-файлов — сценарии пишутся прямо в Rust с помощью макросов. Идеален, если вы не хотите тащить отдельный парсер.

Мы сосредоточимся на cucumber, так как он даёт максимальную гибкость и соответствует стандарту BDD.

Пишем первый BDD-сценарий на Rust

Давайте создадим простой калькулятор скидок. Бизнес-правило: если сумма покупки больше 1000 рублей, скидка 10%. Иначе — 0%.

Шаг 1. Установка зависимостей

Добавьте в Cargo.toml:

[dev-dependencies]
cucumber = "0.20"
tokio = { version = "1", features = ["full"] }
async-trait = "0.1"

Шаг 2. Создаём feature-файл

Файл tests/features/discount.feature:

Feature: Расчёт скидки
  As a покупатель
  I want получить скидку на большую покупку
  So that экономить деньги

  Scenario: Покупка больше 1000 рублей
    Given сумма покупки равна "1500"
    When я запрашиваю расчёт скидки
    Then размер скидки должен быть "150"

  Scenario: Покупка меньше 1000 рублей
    Given сумма покупки равна "500"
    When я запрашиваю расчёт скидки
    Then размер скидки должен быть "0"

Шаг 3. Пишем шаги на Rust

Создайте tests/steps/discount_steps.rs:

use cucumber::{given, then, when, World};
use async_trait::async_trait;

#[derive(Debug, Default)]
struct DiscountWorld {
    purchase_amount: f64,
    discount: f64,
}

#[async_trait(?Send)]
impl World for DiscountWorld {
    // Пустая реализация для простоты
    async fn new() -> Self {
        Self::default()
    }
}

#[given(regex = r"сумма покупки равна \"(\d+)\"")]
async fn set_purchase_amount(world: &mut DiscountWorld, amount: String) {
    world.purchase_amount = amount.parse().unwrap();
}

#[when(regex = r"я запрашиваю расчёт скидки")]
async fn calculate_discount(world: &mut DiscountWorld) {
    if world.purchase_amount > 1000.0 {
        world.discount = world.purchase_amount * 0.1;
    } else {
        world.discount = 0.0;
    }
}

#[then(regex = r"размер скидки должен быть \"(\d+)\"")]
async fn check_discount(world: &mut DiscountWorld, expected: String) {
    let expected: f64 = expected.parse().unwrap();
    assert_eq!(world.discount, expected);
}

Шаг 4. Запускаем тесты

Создайте tests/main.rs:

use cucumber::Cucumber;

mod steps;

#[tokio::main]
async fn main() {
    Cucumber::::new()
        .features(&["./tests/features"])
        .run()
        .await;
}

Запустите: cargo test --test main. Вы увидите зелёные отчёты с прохождением сценариев.

Продвинутые техники BDD на Rust

Параметризация и таблицы

Gherkin поддерживает таблицы данных. Например, для тестирования нескольких сценариев сразу:

Scenario Outline: Проверка скидки для разных сумм
  Given сумма покупки равна "<amount>"
  When я запрашиваю расчёт скидки
  Then размер скидки должен быть "<discount>"

  Examples:
    | amount | discount |
    | 500    | 0        |
    | 1500   | 150      |
    | 2000   | 200      |

Работа с асинхронностью и состоянием

В реальных проектах часто нужно тестировать базы данных или HTTP-запросы. Cucumber поддерживает async из коробки. Пример с мок-сервером:

#[given("база данных содержит пользователя")]
async fn setup_db(world: &mut AppWorld) {
    world.db.insert_user("test_user").await;
}

#[when("я отправляю POST запрос на /login")]
async fn send_login(world: &mut AppWorld) {
    world.response = world.client
        .post("http://localhost:8080/login")
        .json(&serde_json::json!({"user": "test_user"}))
        .                

Блоги

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