Unit Testing
Testing Fundamentals
Automated software testing verifies that code behaves as expected across normal inputs, boundary conditions, and error cases. Rust includes a built-in test framework directly in rustc and cargo, requiring no external testing libraries to write and execute unit tests, integration tests, and documentation tests.
+-------------------------------------------------------------------+
| Rust Testing Pyramid |
| |
| / \ Documentation Tests (`/// ``` ... ````) |
| / \ Verifies public API examples stay runnable |
| /-----\ |
| / \ Integration Tests (`tests/*.rs`) |
| / \ Tests public crate API as an external caller |
| /-----------\ |
| / \ Unit Tests (`#[cfg(test)] mod tests`) |
| /_______________\ Tests internal functions & modules in-place |
+-------------------------------------------------------------------+
Categories of Tests in Rust
| Test Category | Location | Visibility Access | Compilation Model |
|---|---|---|---|
| Unit Tests | Inside src/*.rs (#[cfg(test)] mod tests) |
Public and private items in the module hierarchy | Compiled into the crate's test binary via rustc --test |
| Integration Tests | Separate tests/*.rs files at package root |
Public (pub) items only (links crate as external dependency) |
Each tests/*.rs file compiles into its own standalone test binary |
| Documentation Tests | Code blocks inside /// or //! doc comments |
Public (pub) items only |
Extracted and compiled by rustdoc --test (for library targets) |
How rustc --test Works
When you run cargo test, Cargo compiles your crate with the --test flag:
- The compiler enables the
cfg(test)conditional compilation predicate. - It collects every function annotated with
#[test]across the crate. - Instead of calling
fn main(), it synthesizes a dedicated test harness runner (libtest) that spawns threads to execute tests in parallel, captures their standard output, catches panics, and reports a summary of passed, failed, and ignored tests.
pub fn clamp_percentage(value: i32) -> u8 {
value.clamp(0, 100) as u8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clamps_within_bounds() {
assert_eq!(clamp_percentage(-25), 0);
assert_eq!(clamp_percentage(42), 42);
assert_eq!(clamp_percentage(180), 100);
}
}
fn main() {
println!("Clamped 180 -> {}%", clamp_percentage(180));
}
Clamped 180 -> 100%
Running cargo test compiles the test harness and executes tests::clamps_within_bounds:
running 1 test
test tests::clamps_within_bounds ... ok
test result: ok. 1 passed; 0 failed; 0 filtered out; finished in 0.00s
Pass and Failure Semantics
A Rust test passes or fails based on two rules:
- Returning
(): The test passes if the function returns normally without panicking, and fails if the thread panics (for example, viaassert!,assert_eq!,panic!, or.unwrap()). - Returning
Result<(), E>: The test passes if it returnsOk(()), and fails if it returnsErr(err)or panics.