Rust Testing
Writing reliable tests for your Rust code
๐งช What is Testing in Rust?
Testing in Rust is built into the language and toolchain. Write unit tests, integration tests, and documentation tests to ensure code reliability and catch bugs early in development.
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
Types of Tests
Unit Tests
Test individual functions and modules
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
Integration Tests
Test how parts work together
// tests/integration_test.rs
use my_crate::public_api;
Doc Tests
Test code examples in documentation
/// ```
/// assert_eq!(add(1, 2), 3);
/// ```
Benchmark Tests
Measure performance of code
#[bench]
fn bench_add(b: &mut Bencher) {
b.iter(|| add(2, 3));
}
๐น Unit Tests
Write tests alongside your code using the #[test] attribute:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err("Cannot divide by zero".to_string())
} else {
Ok(a / b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_positive() {
assert_eq!(add(2, 3), 5);
assert_eq!(add(0, 0), 0);
}
#[test]
fn test_divide_success() {
assert_eq!(divide(10.0, 2.0), Ok(5.0));
}
#[test]
fn test_divide_by_zero() {
assert_eq!(divide(10.0, 0.0), Err("Cannot divide by zero".to_string()));
}
}
Run tests:
$ cargo test
running 3 tests
test tests::test_add_positive ... ok
test tests::test_divide_success ... ok
test tests::test_divide_by_zero ... ok
๐น Test Assertions
Rust provides several assertion macros for testing:
#[cfg(test)]
mod tests {
#[test]
fn test_assertions() {
// Basic equality
assert_eq!(2 + 2, 4);
assert_ne!(2 + 2, 5);
// Boolean assertions
assert!(true);
assert!(!false);
// Custom messages
assert_eq!(2 + 2, 4, "Math is broken!");
// Floating point comparison
let result = 0.1 + 0.2;
assert!((result - 0.3).abs() < f64::EPSILON);
}
#[test]
#[should_panic]
fn test_panic() {
panic!("This test should panic");
}
#[test]
#[should_panic(expected = "divide by zero")]
fn test_specific_panic() {
let _result = 10 / 0;
}
}
๐น Integration Tests
Create integration tests in the tests/ directory:
๐ธ tests/integration_test.rs
use my_crate::{add, divide};
#[test]
fn test_public_api() {
// Test the public interface
assert_eq!(add(5, 7), 12);
match divide(10.0, 2.0) {
Ok(result) => assert_eq!(result, 5.0),
Err(_) => panic!("Unexpected error"),
}
}
#[test]
fn test_error_handling() {
match divide(10.0, 0.0) {
Ok(_) => panic!("Should have returned an error"),
Err(msg) => assert_eq!(msg, "Cannot divide by zero"),
}
}
Integration test features:
- Test your crate as an external user would
- Only test public API
- Each file is a separate crate
-
Run with
cargo test
๐น Documentation Tests
Test code examples in your documentation:
/// Adds two numbers together.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
///
/// # Panics
///
/// This function doesn't panic under normal circumstances.
///
/// ```should_panic
/// // This example will panic
/// panic!("This is a panic example");
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
# Run documentation tests
cargo test --doc
# Test specific documentation
cargo test --doc add
๐น Test Organization
Organize tests effectively in your project:
my_project/
โโโ src/
โ โโโ lib.rs # Unit tests here
โ โโโ math.rs # Unit tests here
โ โโโ utils.rs # Unit tests here
โโโ tests/ # Integration tests
โ โโโ integration_test.rs
โ โโโ api_test.rs
โ โโโ common/ # Shared test utilities
โ โโโ mod.rs
โโโ benches/ # Benchmark tests
โโโ my_benchmark.rs
๐ธ Shared test utilities
// tests/common/mod.rs
pub fn setup_test_environment() -> TestConfig {
TestConfig {
database_url: "sqlite::memory:".to_string(),
debug_mode: true,
}
}
// tests/integration_test.rs
mod common;
#[test]
fn test_with_setup() {
let config = common::setup_test_environment();
// Use config in test...
}
๐น Test Commands
Various ways to run and control tests:
# Run all tests
cargo test
# Run specific test
cargo test test_add
# Run tests with pattern
cargo test divide
# Run tests in specific module
cargo test tests::math
# Run with output
cargo test -- --nocapture
# Run ignored tests
cargo test -- --ignored
# Run single-threaded
cargo test -- --test-threads=1
Test attributes:
- #[test]: Mark function as test
- #[should_panic]: Test should panic
- #[ignore]: Skip test by default
- #[cfg(test)]: Only compile for tests