Welcome to Day 21 of my Rust journey! Today, I delved deep into the mechanisms Rust provides for organizing and running tests. Yesterday, I explored writing test functions and macros like assert!
, assert_eq!
, and assert_ne!
. Today was about running those tests efficiently, filtering and organizing them, and understanding the distinctions between unit and integration tests.
🧪 How Rust Runs Tests
Rust’s cargo test
command compiles your code in test mode and runs the resultant test binary. By default, it:
- Runs all tests in parallel using threads.
- Captures output of passing tests for a cleaner report.
You can customize this behavior using:
-
cargo test --help
: View Cargo’s test options. -
cargo test -- --help
: View options passed to the test binary.
🧵 Running Tests in Parallel or Consecutively
Rust runs tests in parallel by default to speed up feedback. But this can cause issues if tests share state (e.g., the same file, directory, environment variables).
Avoid shared state or use:
cargo test -- --test-threads=1
This command disables parallelism and runs tests serially.
👁 Showing Output for Passing Tests
By default, output (println!
) in passing tests is hidden.
To view it:
cargo test -- --show-output
Example
fn prints_and_returns_10(a: i32) -> i32 {
println!("I got the value {a}");
10
}
#[test]
fn this_test_will_pass() {
let value = prints_and_returns_10(4);
assert_eq!(value, 10);
}
Use --show-output
to view the printed output.
🎯 Running a Subset of Tests by Name
You don’t have to run all tests.
Run a single test:
cargo test one_hundred
Example:
#[test]
fn one_hundred() {
assert_eq!(100, 10 * 10);
}
Run tests with partial name match:
cargo test add
This runs all tests with add
in the name.
Example:
#[test]
fn add_two_numbers() {
assert_eq!(2 + 2, 4);
}
#[test]
fn add_three_numbers() {
assert_eq!(1 + 2 + 3, 6);
}
🔕 Ignoring Expensive Tests
You can ignore long-running tests by default and run them only when needed.
#[test]
#[ignore]
fn expensive_test() {
// takes time
}
Run ignored tests only:
cargo test -- --ignored
Run all tests including ignored:
cargo test -- --include-ignored
🗂 Test Organization: Unit Tests vs Integration Tests
🧪 Unit Tests
- Placed inside
src/
next to the code. - Use
#[cfg(test)]
to compile only during testing. - Access private items.
// src/lib.rs
pub fn add(x: u32, y: u32) -> u32 {
x + y
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(add(2, 2), 4);
}
}
🧩 Integration Tests
- Placed in
tests/
directory. - Compiled as separate crates.
- Only access public API.
Example:
// tests/integration_test.rs
use my_crate::add;
#[test]
fn it_adds_two() {
assert_eq!(add(2, 2), 4);
}
🧱 Shared Setup for Integration Tests
Put reusable code in tests/common/mod.rs
:
// tests/common/mod.rs
pub fn setup() {
// Setup code here
}
Use it like this:
mod common;
#[test]
fn it_adds_two() {
common::setup();
assert_eq!(add(2, 2), 4);
}
🧠 Summary
- Rust allows parallel and serial test execution.
- Use filtering to speed up your workflow.
- Structure matters: unit vs integration.
- Output visibility, ignored tests, and subsetting are all customizable.
- Integration tests live in their own crates under
tests/
.
That’s all for today.
Top comments (0)