Rust ships testing in the toolchain, and well-tested crates are the norm in the ecosystem.
A test is a function annotated #[test]. It passes if it returns normally and fails if it panics. Assertions are macros: assert!(cond), assert_eq!(left, right), assert_ne!, each with optional format arguments for a custom message. assert_eq! prints both values on failure, so the compared types must implement Debug and PartialEq.
Unit tests conventionally live in the same file as the code, in a child module:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn works() { assert_eq!(add(2, 2), 4); }
}#[cfg(test)] compiles the module only for cargo test, and because it is a child module it can test private functions.
Integration tests go in the tests/ directory. Each file there is a separate crate that uses your library like an external user would, through its public API only.
Doc tests: code blocks in /// documentation comments are compiled and run by cargo test, which keeps examples in your docs honest.
Other tools:
- A test can return
Result<(), E>and use?; returningErrfails it. #[should_panic(expected = "divide by zero")]asserts a panic with matching message.#[ignore]skips slow tests unless you runcargo test -- --ignored.cargo test parseruns only tests whose names containparse;cargo test -- --nocaptureshowsprintln!output.
Tests run in parallel threads by default, so tests that touch shared state like files or environment variables need unique paths or --test-threads=1. Popular additions include proptest for property testing and insta for snapshot tests.
Example
pub fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 { return Err("divide by zero".into()); }
Ok(a / b)
}
fn clamp_pct(x: i32) -> i32 { x.clamp(0, 100) } // private, still testable
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn divides() { assert_eq!(divide(7, 2), Ok(3), "integer division truncates"); }
#[test]
fn clamps() { assert!(clamp_pct(150) == 100); }
#[test]
fn with_question_mark() -> Result<(), String> {
assert_ne!(divide(10, 5)?, 0);
Ok(())
}
#[test]
#[should_panic(expected = "divide by zero")]
fn zero_panics() { divide(1, 0).unwrap(); }
}Remember
#[test]functions pass unless they panic; useassert_eq!for good failure output- Unit tests sit in a
#[cfg(test)] mod testsand can reach private items tests/holds integration tests;///examples run as doc tests
Watch out for
- Tests that share a temp file or env var and fail randomly because tests run in parallel.