Rust — Testing
Tests are built into the language
No framework needed. cargo test discovers and runs them:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*; // pull in the parent module's items
#[test]
fn adds_two_numbers() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn handles_negatives() {
assert_eq!(add(-1, -1), -2);
}
}
The #[cfg(test)] module compiles ONLY during tests — zero cost in release builds.
The assert family
| Macro | Fails unless… |
|---|---|
assert!(expr) | expression is true |
assert_eq!(a, b) | values are equal |
assert_ne!(a, b) | values differ |
All three print both values on failure — assert_eq! is your bread and butter:
assertion `left == right` failed
left: 6
right: 5
Custom failure messages come second:
assert_eq!(result, 10, "score calculation broke for input {}", input);
Expected failures
Test that code panics correctly:
#[test]
#[should_panic]
fn panics_on_zero() {
divide(10, 0);
}
// stricter — check the message:
#[test]
#[should_panic(expected = "division by zero")]
fn panics_with_message() {
divide(10, 0);
}
Results instead of panics
Tests returning Result<(), E> can use ?:
#[test]
fn parses_number() -> Result<(), std::num::ParseIntError> {
let n: i32 = "42".parse()?;
assert_eq!(n, 42);
Ok(())
}
Running tests
$ cargo test # run everything
$ cargo test adds # filter by name substring
$ cargo test -- --ignored # include #[ignore]d tests
$ cargo test -- --nocapture # see println! output
Unit tests print nothing by default (output swallowed per-test); --nocapture reveals it while debugging.
Integration tests — external user's view
Files under tests/ compile as SEPARATE crates using only your public API:
myapp/
├── src/lib.rs ← pub fn divide(...)
└── tests/
└── api.rs ← use myapp::divide;
// tests/api.rs
use myapp::divide;
#[test]
fn division_works() {
assert_eq!(divide(10, 2), Ok(5));
}
Convention: unit tests live beside code (private access, fast feedback); integration tests in tests/ verify the public surface.
What to actually test
Priority order for beginners:
- Edge cases — empty inputs, zero, negative numbers, max values (
i32::MAX) - Expected failures — invalid parse input should error/panic correctly
- Core logic branches — each if/match arm at least once
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_slice_gives_zero() {
assert_eq!(mean(&[]), 0.0);
}
#[test]
fn single_element() {
assert_eq!(mean(&[5]), 5.0);
}
}
Culture note: Rust projects test heavily because it's frictionless — no setup, instant runs, doctests included. Match that norm early and debugging sessions shrink.
Mini Practice
- Write add() with three unit tests including negatives.
- Make one fail deliberately; read the left/right output.
- Add #[should_panic] coverage for an unwrap.
- Create tests/api.rs integration test for your lib function.
- Run filtered:
cargo test empty.
Rust track complete — all syllabus topics now have full-length lessons. 🦀✅
Related Topics
Frequently Asked Questions about Testing
What is Testing in Rust?
Testing is a fundamental concept in Rust. This lesson explains it step by step with clear examples, making it easy for beginners to understand.
How do I learn Testing?
Start by reading the explanation above, then try the code examples. Practice by modifying the examples and experimenting with different values. Hands-on practice is the best way to learn Testing.
Why is Testing important in Rust?
Testing is essential for Rust development. Understanding this concept will help you write better code and solve real-world problems more effectively.