rstest‑bdd seeks to bring the collaborative clarity of Behaviour‑Driven
Development to Rust without sacrificing the ergonomics of rstest and the
convenience of cargo test. In its present form, the framework provides a core
workflow: write Gherkin scenarios, implement matching Rust functions with
#[given], #[when] and #[then] annotations, rely on matching parameter
names for fixture injection (use #[from] when renaming), and bind tests to
scenarios with #[scenario]. Step definitions are discovered at link time via
the inventory crate, and scenarios execute all steps in sequence before
running any remaining test code. While advanced Gherkin constructs and
parameterization remain on the horizon, this foundation allows teams to
integrate acceptance criteria into their Rust test suites and to engage all
three amigos in the specification process.
Feature file rebuild invalidation
Since v0.6.0, #[scenario] and scenarios! register every bound .feature
file as a Cargo rebuild dependency: the macro emits an include_bytes! binding
whose path is built from CARGO_MANIFEST_DIR, so rustc records the file in
dep-info and Cargo recompiles the scenario binary whenever the file changes.
Editing only a .feature file therefore recompiles the crate, and the tests
reflect the new text immediately.
#[scenario] tracks its single bound file. scenarios! tracks every
.feature file it discovers in the bound directory — including files whose
scenarios are excluded by a tags = filter.
Generated scenario metadata renders feature paths as manifest-relative paths
with / separators when the file is inside the consuming crate's manifest
directory. A file outside that directory remains absolute when it shares the
same filesystem root; an unrelated root is rejected at compile time. The
rendered value is carried by ScenarioMetadata::feature_path and appears in
JSON and JUnit reports and in cargo bdd --dump-steps output.
Per-file tracking covers edits to files that existed when the macro ran. It
cannot see a file that is added afterwards, because nothing referenced a file
that did not exist at expansion time. To close that gap, add a build.rs to
the consuming crate. It tells Cargo to rerun the build script whenever the
bound directory changes:
fn main() {
println!("cargo::rerun-if-changed=tests/features");
}
Cargo scans a rerun-if-changed directory recursively, so a single
directory line covers new files and subdirectories; a hand-maintained per-file
list can silently omit a new subdirectory, which is the failure mode this
mechanism exists to prevent. The snippet above is executed by this repository's
test suite (it is extracted from this document and run against a scratch crate,
see the [developers' guide][developers-guide]), so it cannot rot.
Cost
Per-file tracking makes a previously free operation cost something: editing one
.feature file now rebuilds the whole test binary containing the scenarios!
invocation that binds it, which re-parses every feature file in the bound
directory. Measured at roughly 5.4 s for 100 files with five scenarios each on
a development host. For large feature directories, split them across several
test binaries with disjoint scenarios! invocations.