Why BDD in Rust is Not About Boring Tests
\n\nImagine: you are writing a high-load service in Rust. The code is safe, fast, but when the product reaches the client, they say: "I thought the button should work differently." Sound familiar? The classic gap between code and expectations. This is where BDD (Behaviour-Driven Development) comes to the rescue — a methodology that translates business requirements into a language understandable by both the manager and the Rust compiler.
\n\nIn this article, we will explore how to implement BDD in Rust in practice. You will learn how to write scenarios in human language (Gherkin), automate them using cucumber and specs, and why this makes your code not only reliable but also transparent for the entire team. No fluff — only living code and real examples.
What is BDD and Why Rust is Ideal for It
\n\nBehaviour-Driven Development is an evolution of TDD. While TDD focuses on "how to test code," BDD answers the question "what behavior do we want to see." You write scenarios in the Gherkin language (Given-When-Then), which serve as both tests and documentation.
\n\nRust is a language that loves strictness and predictability. BDD in Rust provides:
\n- \n
- Self-documenting code — any developer reads the scenario and understands the business logic. \n
- Type-level safety — the compiler checks that your steps match the structure. \n
- Parallel execution — BDD tests in Rust run fast thanks to
asyncand multithreading. \n
When is BDD in Rust Especially Useful?
\n- \n
- API services with clear contracts (REST, GraphQL). \n
- Systems with business rules (finance, logistics, games). \n
- Microservices where integration between components is important. \n
Tools for BDD in Rust: Cucumber and Specs
\n\nThe Rust ecosystem offers two main approaches:
\n\n1. cucumber — the classic BDD framework
\nAllows you to write scenarios in Gherkin (.feature files) and bind them to Rust code. Supports async, custom macros, and reports.
2. specs — a lightweight and fast alternative
\nDoes not require Gherkin files — scenarios are written directly in Rust using macros. Ideal if you don't want to drag in a separate parser.
\n\nWe will focus on cucumber, as it provides maximum flexibility and conforms to the BDD standard.
\n\nWriting Your First BDD Scenario in Rust
\n\nLet's create a simple discount calculator. Business rule: if the purchase amount is greater than 1000 rubles, the discount is 10%. Otherwise — 0%.
\n\nStep 1. Install Dependencies
\nAdd to Cargo.toml:
[dev-dependencies]\ncucumber = "0.20"\ntokio = { version = "1", features = ["full"] }\nasync-trait = "0.1"\n\n\nStep 2. Create a Feature File
\nFile tests/features/discount.feature:
Feature: Discount Calculation\n As a customer\n I want to get a discount on a large purchase\n So that I can save money\n\n Scenario: Purchase over 1000 rubles\n Given the purchase amount is "1500"\n When I request the discount calculation\n Then the discount amount should be "150"\n\n Scenario: Purchase under 1000 rubles\n Given the purchase amount is "500"\n When I request the discount calculation\n Then the discount amount should be "0"\n\n\nStep 3. Write Steps in Rust
\nCreate tests/steps/discount_steps.rs:
use cucumber::{given, then, when, World};\nuse async_trait::async_trait;\n\n#[derive(Debug, Default)]\nstruct DiscountWorld {\n purchase_amount: f64,\n discount: f64,\n}\n\n#[async_trait(?Send)]\nimpl World for DiscountWorld {\n // Empty implementation for simplicity\n async fn new() -> Self {\n Self::d