BDD Pattern¶
Every rtb-* crate that has user-visible behaviour ships Gherkin scenarios
alongside its unit tests. This page documents the canonical wiring.
Layout¶
crates/rtb-foo/
├── Cargo.toml
├── src/
├── tests/
│ ├── bdd.rs # cargo test integration harness entry point
│ ├── unit.rs # traditional #[test] fns (runs via cargo test / nextest)
│ ├── features/ # .feature files — Gherkin scenarios
│ │ └── foo.feature
│ └── steps/ # step definitions
│ ├── mod.rs
│ └── foo_steps.rs
Cargo.toml additions¶
[dev-dependencies]
cucumber = { workspace = true }
tokio = { workspace = true } # async steps
insta = { workspace = true } # snapshot assertions when useful
No [[test]] stanza is required — tests/bdd.rs is picked up automatically
by Cargo as an integration test, and we run it with the default libtest
harness via cucumber::World's blocking or tokio-aware entry point.
tests/bdd.rs¶
mod steps;
use cucumber::World;
use steps::FooWorld;
#[tokio::test(flavor = "multi_thread")]
async fn bdd() {
FooWorld::cucumber()
.fail_on_skipped()
.run_and_exit("tests/features")
.await;
}
Using #[tokio::test] wraps the runner in the standard libtest harness —
the scenarios therefore run under cargo test (and cargo nextest run)
with no extra flags. fail_on_skipped() makes a missing step a hard error
rather than a silent skip.
tests/steps/mod.rs¶
pub mod foo_steps;
use cucumber::World;
#[derive(Debug, Default, World)]
pub struct FooWorld {
// Scenario-local state.
}
Gherkin style¶
- One
.featurefile per user-visible capability, not per struct. - Use business language in scenarios —
Given a tool with release source "github:…", notGiven a struct with field release_source. - Background blocks for shared setup.
- Tag scenarios that require network with
@onlineso they can be filtered out of offline CI runs (currently our CI is fully offline — no@onlinescenarios until we introduce a dedicated job).
Running¶
just test-bdd # BDD only
just test # full test suite including BDD
cargo test -p rtb-foo --test bdd -- --name "renders help" # single scenario
Why cucumber-rs and not alternatives¶
cucumber-rsis the idiomatic Rust Gherkin runner; macros deriveWorldand step definitions with compile-time type safety.- Cargo-test-integrated (as shown) means nextest, CI, and IDE test runners all treat scenarios as first-class tests. No separate binary, no bespoke runner.
- Alternatives considered:
gherkin-rust(parser only, no runner), hand-rolled macros (reinvents the wheel). Neither is worth the overhead.