Rust Testing
Rust has testing built directly into the language and cargo, so you don’t need to reach for a separate library to get started. This page covers just enough to write and run your first tests.
Writing a basic test
Section titled “Writing a basic test”A test is a regular function marked with #[test]:
fn add(a: i32, b: i32) -> i32 { a + b}
#[cfg(test)]mod tests { use super::*;
#[test] fn it_adds_two_numbers() { assert_eq!(add(2, 3), 5); }}A few things worth pointing out:
#[cfg(test)]tells Rust this module is only compiled when running tests — it’s not part of your normal program.use super::*;brings in everything from the parent module (here, theaddfunction), so the test can call it.#[test]marks a specific function as a test thatcargo testshould run.
Running tests
Section titled “Running tests”From your project folder:
cargo testYou’ll see output like:
running 1 testtest tests::it_adds_two_numbers ... ok
test result: ok. 1 passed; 0 failed; 0 ignoredassert! and friends
Section titled “assert! and friends”Three macros cover most of what you’ll need:
#[test]fn checks() { assert!(2 + 2 == 4); // fails the test if the condition is false assert_eq!(2 + 2, 4); // fails if the two values aren't equal assert_ne!(2 + 2, 5); // fails if the two values ARE equal}assert_eq! and assert_ne! are usually preferred over a plain assert!(a == b) — when they fail, they print both values, so you can immediately see what didn’t match instead of just “assertion failed.”
A failing test, and what it looks like
Section titled “A failing test, and what it looks like”fn add(a: i32, b: i32) -> i32 { a + b}
#[cfg(test)]mod tests { use super::*;
#[test] fn this_will_fail() { assert_eq!(add(2, 2), 5); }}test tests::this_will_fail ... FAILED
assertion `left == right` failed left: 4 right: 5That’s exactly the kind of message you want when a test fails — it tells you what your code actually produced versus what the test expected.
Testing that a function panics
Section titled “Testing that a function panics”Sometimes the correct behavior is a panic — say, a function that should refuse invalid input. #[should_panic] tests for that:
fn divide(a: i32, b: i32) -> i32 { if b == 0 { panic!("cannot divide by zero"); } a / b}
#[cfg(test)]mod tests { use super::*;
#[test] #[should_panic] fn dividing_by_zero_panics() { divide(10, 0); }}Better failure messages
Section titled “Better failure messages”When a test fails, you want to know why as fast as possible. Two small additions help a lot.
A custom message. assert!, assert_eq! and assert_ne! all accept an extra message after the values, written just like a println!. It’s printed only if the check fails.
expected on should_panic. A plain #[should_panic] passes for any panic, even one caused by a different bug. Adding expected = "..." makes the test pass only if the panic message contains that text.
Here’s the divide function again, with a main so you can run it, and two tests that use both ideas:
fn divide(a: i32, b: i32) -> i32 { if b == 0 { panic!("cannot divide by zero"); } a / b}
fn main() { println!("10 / 2 = {}", divide(10, 2)); println!("7 / 2 = {}", divide(7, 2));}
#[cfg(test)]mod tests { use super::*;
#[test] fn rounds_down() { let result = divide(7, 2); assert_eq!(result, 3, "7 / 2 should round down, but got {result}"); }
#[test] #[should_panic(expected = "divide by zero")] fn dividing_by_zero_panics() { divide(10, 0); }}Output:
10 / 2 = 57 / 2 = 3The output above is what cargo run prints, because main doesn’t run the tests. To run the tests, use cargo test. In the Rust Playground, open the menu next to Run and pick Test. You’ll see:
running 2 teststest tests::rounds_down ... oktest tests::dividing_by_zero_panics - should panic ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered outTests run in parallel, so the two test ... lines may show up in either order. Now imagine the test expected 4 instead of 3. The failure would include your message, right next to the two values:
assertion `left == right` failed: 7 / 2 should round down, but got 3 left: 3 right: 4And if divide panicked with a different message, the expected check catches it:
note: panic did not contain expected string panic message: "cannot divide by zero" expected substring: "negative"Why bother writing tests
Section titled “Why bother writing tests”The honest answer: for a short script, you probably won’t. Tests start paying off once code is going to be reused or changed later — they let you refactor with confidence, because cargo test will tell you immediately if you broke something, instead of finding out from a user.
Tests can also live in your documentation: code examples inside /// doc comments run with cargo test too, so your examples can never go out of date. The Documentation page shows how.
Practice
Section titled “Practice”For both exercises, the solution has a main you can run, and tests you run with cargo test (or Test in the Playground).
1. Test a grading function
Section titled “1. Test a grading function”Write fn grade(score: u32) -> char that returns 'A' for 90 and up, 'B' for 80 to 89, 'C' for 70 to 79, and 'F' for anything lower. Then write tests that check the edges, like 90 and 89. Hint: edges are where bugs hide. A test for exactly 90 catches the classic mistake of writing > instead of >=.
Show solution
fn grade(score: u32) -> char { if score >= 90 { 'A' } else if score >= 80 { 'B' } else if score >= 70 { 'C' } else { 'F' }}
fn main() { for score in [95, 85, 72, 40] { println!("{score} -> {}", grade(score)); }}
#[cfg(test)]mod tests { use super::*;
#[test] fn edges_of_a() { assert_eq!(grade(90), 'A'); assert_eq!(grade(89), 'B'); }
#[test] fn edges_of_c() { assert_eq!(grade(70), 'C'); assert_eq!(grade(69), 'F'); }
#[test] fn zero_is_f() { assert_eq!(grade(0), 'F', "a score of 0 should fail"); }}Output:
95 -> A85 -> B72 -> C40 -> Fcargo test should report 3 passed. Try changing score >= 90 to score > 90 and run the tests again: edges_of_a fails and shows you left: 'B' and right: 'A'.
2. Test a function that returns an Option
Section titled “2. Test a function that returns an Option”Write fn safe_divide(a: i32, b: i32) -> Option<i32> that returns None when b is 0 instead of panicking. Write one test for a normal division and one for dividing by zero. Hint: assert_eq! works with Option too, so you can write assert_eq!(safe_divide(1, 0), None).
Show solution
fn safe_divide(a: i32, b: i32) -> Option<i32> { if b == 0 { None } else { Some(a / b) }}
fn main() { println!("{:?}", safe_divide(10, 2)); println!("{:?}", safe_divide(1, 0));}
#[cfg(test)]mod tests { use super::*;
#[test] fn divides_normally() { assert_eq!(safe_divide(10, 2), Some(5)); }
#[test] fn zero_gives_none() { assert_eq!(safe_divide(1, 0), None); }}Output:
Some(5)NoneCompare this with the divide function above. There, dividing by zero is a panic, so the test needs #[should_panic]. Here it’s just a normal return value, so a plain assert_eq! is enough. Functions that return Option or Result are usually easier to test.
- Mark a function
#[test]to make it a test; run all tests withcargo test. assert!,assert_eq!, andassert_ne!cover most everyday checks — preferassert_eq!/assert_ne!for better failure messages.#[should_panic]tests that a function panics when it’s supposed to. Addexpected = "..."to check it panicked for the right reason.- Add a custom message to any assert macro to explain what went wrong when it fails.
- Tests are most valuable for code you expect to change or reuse later.
Quick check
Section titled “Quick check”Quick check
1. What attribute marks a function as a test that cargo test should run?
2. Why are assert_eq! and assert_ne! usually preferred over a plain assert!(a == b)?
3. What does #[should_panic] do?
Score: 0 / 3
Read more
Section titled “Read more”- How to Write Tests — The Rust Book:
#[test], the assert macros, custom messages,should_panicand tests that returnResult. - Controlling How Tests Are Run — The Rust Book: running one test by name, showing
println!output, and#[ignore]. - Test Organization — The Rust Book: the difference between unit tests and integration tests in a
tests/folder. - Unit testing — Rust by Example: short examples of passing, failing and panicking tests.
cargo test— The Cargo Book: every option of thecargo testcommand.