Testing is a crucial part of software development that helps ensure the correctness and reliability of your code. Rust provides built-in support for writing unit tests, integration tests, and documentation tests. This explanation focuses on unit testing, which tests individual units of code in isolation.
1. Basic Structure of Tests
In Rust, tests are typically written within the same crate as the code they are testing, usually in a `mod tests` block. This module is annotated with `#[cfg(test)]`, which means the code inside it will only be compiled and run when you execute `cargo test`, not when you build your regular application.
Each test function is marked with the `#[test]` attribute. Test functions don't take any arguments and don't return any values. They typically contain assertions that check if the code behaves as expected.
2. Running Tests
To run all tests in your project, navigate to your project's root directory in the terminal and execute:
```bash
cargo test
```
This command compiles your test code, runs all the test functions, and reports the results. You can also run a specific test by providing its name:
```bash
cargo test test_add_positive_numbers
```
Or run tests matching a pattern:
```bash
cargo test add
```
3. Assertions
Assertions are macros that help you check conditions in your tests. If an assertion fails, the test function will panic, and the test runner will report it as a failed test.
* `assert!(condition)`: Asserts that a boolean `condition` is `true`. If `false`, the test fails.
* `assert_eq!(left, right)`: Asserts that `left` and `right` are equal. This is often preferred over `assert!(left == right)` because it prints the values of `left` and `right` if they are not equal, providing more helpful debugging information.
* `assert_ne!(left, right)`: Asserts that `left` and `right` are not equal. Similar to `assert_eq!`, it provides helpful information upon failure.
4. Testing for Panics (`#[should_panic]`)
Sometimes, you want to ensure that a certain piece of code *does* panic under specific conditions (e.g., invalid input). The `#[should_panic]` attribute allows you to test for this.
* `#[should_panic]`: The test passes if the code inside the function panics for any reason.
* `#[should_panic(expected = "some message")]`: The test passes if the code panics *and* the panic message contains the specified string. This makes your panic tests more precise.
5. Ignoring Tests (`#[ignore]`)
If you have tests that are particularly expensive, slow, or rely on external resources that might not always be available, you can mark them with the `#[ignore]` attribute. These tests will not run by default when you execute `cargo test`.
To run only ignored tests:
```bash
cargo test -- --ignored
```
To run *all* tests, including ignored ones:
```bash
cargo test -- --include-ignored
```
6. Test Organization (Unit vs. Integration Tests)
While this explanation focuses on unit tests (tests within the `src` directory, alongside the code), Rust also supports integration tests. Integration tests go in a `tests` directory at the root of your project and test your library as an external consumer would, ensuring different parts work together correctly. Documentation tests are code examples in your documentation comments that are compiled and run as tests.
By following these principles, you can write robust tests that give you confidence in your Rust code.
Example Code
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(numerator: i32, denominator: i32) -> i32 {
if denominator == 0 {
panic!("Cannot divide by zero!");
}
numerator / denominator
}
pub fn is_even(number: i32) -> bool {
number % 2 == 0
}
#[cfg(test)] // This attribute ensures the test module is only compiled when running tests
mod tests {
// Import everything from the outer module into the test module's scope
use super::*;
#[test]
fn test_add_positive_numbers() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative_numbers() {
assert_eq!(add(-2, -3), -5);
}
#[test]
fn test_add_zero() {
assert_eq!(add(0, 5), 5);
assert_eq!(add(5, 0), 5);
assert_eq!(add(0, 0), 0);
}
#[test]
fn test_is_even_true() {
assert!(is_even(4));
}
#[test]
fn test_is_even_false() {
assert!(!is_even(3));
}
#[test]
#[should_panic(expected = "Cannot divide by zero!")]
fn test_divide_by_zero_panics() {
// This test passes only if 'divide' panics with the expected message
divide(10, 0);
}
#[test]
#[should_panic] // This test passes if it panics, regardless of the message
fn test_another_panic_scenario() {
// Simulate another scenario that should panic, e.g., index out of bounds
let _ = vec![1, 2, 3][5];
}
#[test]
#[ignore = "This test is slow and should not run often automatically."]
fn test_long_running_computation() {
// Simulate a long computation, e.g., connecting to a database or complex algorithm
std::thread::sleep(std::time::Duration::from_millis(500)); // Half a second delay
assert_eq!(add(10, 20), 30);
}
}








How to Write Tests