Rust Built-in Testing
Rust Built-in Testing
Overview
Basic testing functionality built into the Rust standard library. Write unit tests using the #[test] attribute and run them with the cargo test command. Create tests with simple syntax without external crate dependencies, using assertion macros (assert!, assert_eq!, assert_ne!, etc.). Serves as the foundation for all Rust testing frameworks, enabling reliable tests that leverage Rust's ownership system and type safety.
Details
Key Features
- Zero Dependencies: Works with standard library only, no external crates required
- Simple Syntax: Define test functions with
#[test]attribute - Parallel Execution: Runs tests in parallel by default for fast test cycles
- Assertion Macros: Intuitive validation with
assert!,assert_eq!,assert_ne! - Panic Testing: Verify expected panics with
#[should_panic] - Test Organization: Separate test code with
#[cfg(test)]modules - Documentation Tests: Automatically test code examples in comments
Architecture
Rust's testing system consists of the following components:
- Test Runner: Test harness compiled and executed by
cargo test - Test Attributes: Markers like
#[test],#[should_panic],#[ignore] - Assertion Macros: Macro collection for validating test conditions
- Test Organization: Separation of unit tests, integration tests, and documentation tests
Test Placement
- Unit Tests: Placed in
srcdirectory modules with#[cfg(test)] - Integration Tests: Placed as independent files in
tests/directory - Documentation Tests: Automatically test code examples in documentation comments
Pros and Cons
Pros
- Built-in: Available immediately upon Rust installation
- Easy to Learn: Simple, intuitive syntax with comprehensive official documentation
- Zero Overhead: No external dependencies, no increase in build time or complexity
- Ownership Integration: Fully integrated with Rust's ownership system, ensuring safe tests
- Parallel Execution: Runs in parallel by default for fast feedback cycles
- Documentation Integration: Code examples function as tests, ensuring they stay up-to-date
Cons
- Basic Features Only: Requires separate crates for parameterized tests or property-based testing
- Assertions: Limited advanced matchers or custom messages
- Test Fixtures: Limited mechanisms for setup/teardown
- Parallel Control: Requires control when tests share resources
References
Code Examples
Basic Tests
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
}
Using Assertion Macros
#[cfg(test)]
mod tests {
#[test]
fn test_assertions() {
// Boolean tests
assert!(true);
assert!(!false);
// Equality tests
assert_eq!(2 + 2, 4);
assert_ne!(2 + 2, 5);
// Custom messages
let result = 2 + 2;
assert_eq!(
result, 4,
"Calculation result differs from expected: expected {}, got {}",
4, result
);
}
}
Testing Panics
pub fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("Division by zero is not allowed");
}
a / b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn test_divide_by_zero() {
divide(10, 0);
}
#[test]
#[should_panic(expected = "Division by zero")]
fn test_divide_by_zero_with_message() {
divide(10, 0);
}
}
Tests Using Result Type
#[cfg(test)]
mod tests {
#[test]
fn test_with_result() -> Result<(), String> {
let result = 2 + 2;
if result == 4 {
Ok(())
} else {
Err(String::from("Calculation result is incorrect"))
}
}
}
Ignoring Tests
#[cfg(test)]
mod tests {
#[test]
fn quick_test() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// Time-consuming test
// Not run normally
// Can run with: cargo test -- --ignored
}
}
Integration Tests
// tests/integration_test.rs
use my_crate;
#[test]
fn test_public_api() {
let result = my_crate::add(2, 3);
assert_eq!(result, 5);
}
Documentation Tests
/// Adds two numbers.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// let result = add(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
Benchmark Tests (nightly required)
#![feature(test)]
extern crate test;
#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;
#[bench]
fn bench_add(b: &mut Bencher) {
b.iter(|| {
add(2, 3)
});
}
}
Controlling Parallel Execution
// Disable parallel execution: cargo test -- --test-threads=1
// Show output: cargo test -- --nocapture
// Run specific test: cargo test test_name
#[cfg(test)]
mod tests {
use std::sync::Mutex;
use std::sync::Arc;
#[test]
fn test_with_shared_resource() {
// When using shared resources,
// synchronize with Mutex etc.
let counter = Arc::new(Mutex::new(0));
let mut num = counter.lock().unwrap();
*num += 1;
assert_eq!(*num, 1);
}
}