Running Tests with Cargo
The cargo test command compiles a package's targets in test mode and runs the resulting test binaries sequentially. Understanding the execution order, target selection flags, and separator syntax (--) allows you to run fast, targeted test loops during development.
`cargo test` Default Execution Pipeline:
1. Unit tests in library target (`src/lib.rs`)
2. Unit tests in binary target(s) (`src/main.rs`, `src/bin/*.rs`)
3. Integration tests (`tests/*.rs`, one binary per file)
4. Documentation tests (`rustdoc --test` on `src/lib.rs`)
If any step in this pipeline fails, cargo test stops immediately unless you pass --no-fail-fast.
Cargo Flags vs Test Binary Flags (-- Separator)
cargo test accepts two distinct sets of flags separated by --:
cargo test [CARGO_OPTIONS] [TESTNAME_FILTER] -- [TEST_BINARY_OPTIONS]
| Flag | Placement | Purpose |
|---|---|---|
--lib |
Before -- |
Runs only the library's unit tests (src/lib.rs), skipping binaries, integration tests, and doc-tests. |
--bins / --bin <name> |
Before -- |
Runs only unit tests inside binary targets (src/main.rs or src/bin/<name>.rs). |
--test <name> |
Before -- |
Runs only the specified integration test file tests/<name>.rs. |
--doc |
Before -- |
Runs only documentation tests. |
--no-run |
Before -- |
Compiles all test binaries without executing them (useful in CI to separate build time from test execution time). |
--no-fail-fast |
Before -- |
Continues running remaining test targets even if an earlier target fails. |
--release |
Before -- |
Compiles and runs tests with [profile.release] optimizations enabled. |
--test-threads=1 |
After -- |
Instructs the test harness binary to run tests sequentially on a single thread instead of in parallel. |
--show-output / --nocapture |
After -- |
Instructs the test harness binary to display standard output for passing tests. |
Controlling Test Parallelism (--test-threads)
By default, the Rust test harness runs all tests in a binary concurrently across a thread pool sized to the host's logical CPU count. Because tests run in parallel within the same OS process:
- Tests must not contend over the same fixed file path (e.g.,
/tmp/test.db), fixed TCP port (127.0.0.1:8080), or global process state (env::set_current_dir). - Use unique temporary paths per test and ephemeral ports (
127.0.0.1:0), or force single-threaded execution when testing unavoidable global state:
cargo test -- --test-threads=1
You can also set the RUST_TEST_THREADS=1 environment variable to control thread parallelism without passing -- --test-threads=1 on the command line.
Verifying Behavior in Both Debug and Release Modes
Because integer overflow checks and debug_assert! macros are enabled in the default test profile (which inherits from dev) and disabled in release (cargo test --release), running both cargo test and cargo test --release in continuous integration catches both debug-assertion failures and optimization-dependent edge cases:
pub fn wrapping_increment(x: u8) -> u8 {
x.wrapping_add(1)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wraps_around_u8_max_in_both_profiles() {
assert_eq!(wrapping_increment(u8::MAX), 0);
}
}
fn main() {
println!("255 + 1 (wrapping) = {}", wrapping_increment(u8::MAX));
}
255 + 1 (wrapping) = 0