Unit testing is an integral part of software development that ensures the correctness and reliability of your code's individual components (units). Rust's robust testing framework, integrated into the language through the standard std::test
module, provides a powerful environment for writing and running tests. This section will explore how to write comprehensive unit tests in Rust, covering various aspects such as test organization, assertions, testing private functions, and more.
Anatomy of a Unit Test A typical Rust unit test is a function annotated with the #[test]
attribute. The function should be defined within a module named tests
in the same file as the code it's testing or in a separate tests.rs
file within the same directory.
// File: my_module.rs
// The code to be tested
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// Unit tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
Assertions Rust's testing framework provides a set of assertion macros for checking conditions within your tests. The most commonly used assertions are assert_eq!
and assert_ne!
, which compare two values for equality or inequality, respectively.
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5); // Passes
assert_ne!(add(2, 3), 6); // Passes
}
Testing Private Functions Unit tests can also test private functions using the #[cfg(test)]
attribute on the module containing the private function. This allows the tests to access the private function.
mod private_tests {
// Private function to be tested
fn multiply(a: i32, b: i32) -> i32 {
a * b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_multiply() {
assert_eq!(multiply(2, 3), 6);
}
}
}
Test Organization and Modules For larger projects, organizing your tests into modules helps maintain clarity and separation of concerns. You can create nested modules to match the module structure of your code.
mod math {
pub mod arithmetic {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
#[cfg(test)]
mod tests {
use super::arithmetic::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
}
}
Running Tests To run your tests, use the cargo test
command. By default, it discovers and runs all tests in your project.
cargo test
Testing Features and Flags Rust's testing framework provides features and flags to enhance test capabilities, such as running tests in parallel, filtering tests by name, and running only specific test modules.
cargo test
: Runs all tests in the project.cargo test -- --help
: Displays available flags and options.
Test Documentation Document your tests using Rustdoc comments to provide context, examples, and usage scenarios. This documentation will help others understand the purpose and behavior of your tests.
Test Setup and Teardown Sometimes you must set up a common state or perform cleanup before and after running tests. To achieve this, Rust provides the #[before]
and #[after]
attributes.
Exercise
Write tests for all the Rust projects you have worked on before now. Include examples and simple practice projects.