Testing in Rust
Rust has excellent built-in testing support. You write tests as functions with the #[test] attribute, and run them with cargo test.
Tests are just regular functions that assert conditions. If an assertion fails, the test fails. If it succeeds, the test passes.
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
fn main() {
println!("Run tests with: cargo test");
}
Try it Yourself ->
Assertion Macros
Rust provides three main assertion macros: assert!, assert_eq!, and assert_ne!. They check conditions and panic with helpful messages if they fail.
#[cfg(test)]
mod tests {
#[test]
fn test_assertions() {
let x = 5;
assert!(x > 0);
assert_eq!(x, 5);
assert_ne!(x, 10);
let name = "Rust";
assert!(name.contains("ust"));
assert_eq!(name.len(), 4);
}
}
fn main() {
println!("Assertion macros: assert!, assert_eq!, assert_ne!");
}
Try it Yourself ->
Testing with Result
Tests can return Result<T, E> instead of panicking. This is useful when you want to use the ? operator in tests.
Try it Yourself ->
Unit Tests in the Same File
Unit tests go in a tests module at the bottom of your source file. This lets them test private functions directly.
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_multiply() {
assert_eq!(multiply(2, 3), 6);
assert_eq!(multiply(-1, 1), -1);
}
}
fn main() {
println!("Unit tests go in a tests module in the same file");
}
Try it Yourself ->
Integration Tests
Integration tests go in the tests/ directory. They test your library from the outside, just like a user would. Each file is a separate test crate.
// In tests/integration_test.rs:
// use my_crate::add;
//
// #[test]
// fn test_add_integration() {
// assert_eq!(add(2, 3), 5);
// }
fn main() {
println!("Integration tests go in the tests/ directory");
println!("Each file is a separate test crate");
}
Try it Yourself ->
#[should_panic]
Use #[should_panic] to test that code panics as expected. This is useful for testing error conditions.
fn divide(a: f64, b: f64) -> f64 {
if b == 0.0 {
panic!("Cannot divide by zero");
}
a / b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Cannot divide by zero")]
fn test_divide_by_zero() {
divide(10.0, 0.0);
}
}
fn main() {
println!("Use #[should_panic] to test expected panics");
}
Try it Yourself ->
Test Organization Best Practices
Put unit tests in the same file as the code they test. Put integration tests in the tests/ directory. Use descriptive test names that explain what's being tested.
Run cargo test to run all tests, cargo test -- --nocapture to see println! output, and cargo test test_name to run specific tests.