Write unit tests
Unit tests in Rust are simple functions marked with the #[test]
attribute that verify that the non-test code is functioning in the expected manner. These functions are only compiled when testing code.
Test functions run the code that you want to test. Then they check the results, often by using the assert!
or assert_eq!
macros.
In the following code example, we define a simple add
function and another add_works
function marked with the #[test]
attribute.
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[test]
fn add_works() {
assert_eq!(add(1, 2), 3);
assert_eq!(add(10, 12), 22);
assert_eq!(add(5, -2), 3);
}
When we execute the command $ cargo test
, our output would look like the following example:
running 1 test
test add_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Test failures
Let's try to include a failing test, just to see how cargo tests
behaves.
#[test]
fn add_fails() {
assert_eq!(add(2, 2), 7);
}
If we run the tests again by using the $ cargo test
command, the output should show that our add_works
test passed. It should also show that add_fails
failed and should include information about the failed call to assert_eq
.
running 2 tests
test add_works ... ok
test add_fails ... FAILED
failures:
---- add_fails stdout ----
thread 'add_fails' panicked at 'assertion failed: `(left == right)`
left: `4`,
right: `7`', src/main.rs:14:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
add_fails
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
Expected failures
In many scenarios, it's important to test if a condition will cause a panic!
.
The should_panic
attribute makes it possible to check for a panic!
. If we add this attribute to our test function, the test passes when the code in the function panics. The test fails when the code doesn't panic.
Now, our add_fails
test function can capture an expected panic and treat it as a passing test.
#[test]
#[should_panic]
fn add_fails() {
assert_eq!(add(2, 2), 7);
}
And our tests results will be:
running 2 tests
test add_works ... ok
test add_fails ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Ignore tests
A function annotated with the [test]
attribute can also be annotated with the [ignore]
attribute. This attribute causes that test function to be skipped during tests.
The [ignore]
attribute may optionally be written with a reason why the test is ignored.
#[test]
#[ignore = "not yet reviewed by the Q.A. team"]
fn add_negatives() {
assert_eq!(add(-2, -2), -4)
}
Ignored test functions will still be type checked and compiled but won't be executed in our tests.
running 3 tests
test add_negatives ... ignored
test add_works ... ok
test add_fails ... ok
test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
The test module
Most unit tests go into a submodule with the #[cfg(test)]
attribute.
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod add_function_tests {
use super::*;
#[test]
fn add_works() {
assert_eq!(add(1, 2), 3);
assert_eq!(add(10, 12), 22);
assert_eq!(add(5, -2), 3);
}
#[test]
#[should_panic]
fn add_fails() {
assert_eq!(add(2, 2), 7);
}
#[test]
#[ignore]
fn add_negatives() {
assert_eq!(add(-2, -2), -4)
}
}
The cfg
attribute controls conditional compilation and will only compile the thing it's attached to if the predicate is true
. The test
compilation flag is issued automatically by Cargo whenever we execute the command $ cargo test
, so it will always be true when we run our tests.
The use super::*;
declaration is necessary for the code inside the add_function_tests
module to access the add
in the outer module.
You can find the code used in this unit at this Rust Playground link.