Testing & benchmarks
Tests live next to the code in a #[cfg(test)] module. cargo test runs everything — unit tests, integration tests, doc tests. For benchmarks, criterion is the standard.
1 · Unit tests — colocated
pub fn add(a: i32, b: i32) -> i32 { a + b }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_positive() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn adds_negative() {
assert_eq!(add(-1, -2), -3);
}
#[test]
#[should_panic(expected = "divide by zero")]
fn divide_by_zero_panics() {
panic!("divide by zero");
}
}Same file. The #[cfg(test)] module is only compiled when running tests, so it adds no overhead to your binary.
2 · Integration tests — top-level tests/
use mycrate::add;
#[test]
fn external_api_works() {
assert_eq!(add(2, 2), 4);
}Files in tests/ at the project root are compiled as separate crates that depend on the library. Use them for end-to-end tests that exercise the public API only.
3 · Doc tests — examples that run
/// Adds two integers.
///
/// # Examples
/// ```
/// use mycrate::add;
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }cargo test runs the code in your doc comments. Examples can't drift from
reality — if they break, CI breaks. This single feature kills most stale-docs problems.
4 · Async tests
#[tokio::test]
async fn fetches_data() {
let result = fetch().await;
assert_eq!(result, "ok");
}
async fn fetch() -> &'static str { "ok" }Use #[tokio::test] instead of #[test]. It wires up a single-threaded runtime per test.
5 · Benchmarks — criterion
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fib(n: u64) -> u64 {
if n < 2 { n } else { fib(n - 1) + fib(n - 2) }
}
fn bench(c: &mut Criterion) {
c.bench_function("fib 20", |b| b.iter(|| fib(black_box(20))));
}
criterion_group!(benches, bench);
criterion_main!(benches);black_box hides the value from the optimiser so the work isn't elided.
cargo bench runs it; results report mean, std-dev, and a comparison
against the previous run.
6 · Common mistakes
- Forgetting
cfg(test). Without it the test module compiles into release builds. - Sharing state across tests. Tests run in parallel by default. Use
--test-threads=1only as a workaround; usually means a missing dependency injection. - Asserting on Debug output — fragile across versions. Use
assert_eq!on structured values.
7 · When it clicks
- You write tests in the same file as the code by reflex.
- Public API examples in doc comments — knowing they're checked.
- Benchmarks before optimising, not after.