A function annotated #[test] is automatically discovered and run by cargo test. Inside a test, the assert!, assert_eq!, and assert_ne! macros check conditions and panic with a diagnostic message if they fail, which cargo test reports as a failed test rather than crashing the whole test run.
A basic test with #[test]
Tests conventionally live in a tests submodule marked #[cfg(test)], which excludes them from a normal (non-test) build entirely — test code adds nothing to the shipped binary.
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*; // brings the parent module's items into scope
#[test]
fn it_adds_two_numbers() {
assert_eq!(add(2, 3), 5);
}
}Run with cargo test, which discovers and runs every #[test] function in the crate.
assert! macros and #[should_panic]
assert_eq!/assert_ne! print both values on failure, which is more useful for debugging than a bare assert!. #[should_panic] flips the expected outcome — the test passes only if the code inside DOES panic.
#[test]
fn it_fails_with_a_clear_message() {
assert_eq!(add(2, 2), 5); // fails, prints: left: 4, right: 5
}
#[test]
#[should_panic(expected = "divide by zero")]
fn it_panics_on_bad_input() {
divide(10, 0); // test PASSES only because this panics
}Testing Result-returning functions
A test function can itself return Result<(), E> and use the ? operator, letting you write tests using the same error-propagation style as the rest of the codebase instead of unwrap-ing everywhere.
#[test]
fn it_works_with_result() -> Result<(), String> {
if add(2, 2) == 4 {
Ok(())
} else {
Err(String::from("two plus two doesn't equal four"))
}
}