Testing
$ cargo new adder --lib- create a new library project named
adder.
- lib.rs
- Cargo.lock
- Cargo.toml
lib.rsis the crate root of the library crate.
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}assert!macro is used to check if the result of the function is true.assert_eq!macro is used to compare the result of the function with the expected value.assert_ne!macro is used to check if the two values are not equal.
#[cfg(test)]attribute is used to compile the test code only when running tests.#[test]attribute is used to define a test function.
$ cargo test
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s- A test fails when something inside the test function panics.
- Each test function runs in a new thread, if the main thread sees that the new thread has panicked, it reports the test as failed.
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
#[test]
fn failing_test() {
panic!("Test failed");
}
}$ cargo test
running 2 tests
test tests::it_works ... ok
test tests::failing_test ... FAILED
failures:
---- tests::failing_test stdout ----
thread 'tests::failing_test' panicked at src\lib.rs:17:9:
Test failedstruct Rectangle {
width: i32,
height: i32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let rect = Rectangle {
width: 5,
height: 4,
};
let small_rect = Rectangle {
width: 4,
height: 3,
};
assert!(rect.can_hold(&small_rect));
}
}$ cargo test
running 1 test
test tests::it_works ... ok
successes:
successes:
tests::it_works
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00suse super::*brings theRectanglestruct intotestsmodule scope.
Custom Failure Messages
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_working() {
let result = add(2, 2);
assert_eq!(result, 5, "2 + 2 should be 5, not {}", result);
}
}$ cargo test
running 1 test
test tests::add_working ... FAILED
successes:
failures:
---- tests::add_working stdout ----
thread 'tests::add_working' panicked at src\lib.rs:63:9:
assertion `left == right` failed: 2 + 2 should be 5, not 4Asserting that a Function Panics
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn add_working() {
let result = add(2, 2);
assert_eq!(result, 5, "2 + 2 should be 5, not {}", result);
}
}$ cargo test
running 2 tests
test tests::it_works ... ok
test tests::add_working - should panic ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s#[should_panic]attribute is used to check if the test function panics.
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!("Guess Value must be greater than 0, got {}", value);
} else if value > 100 {
panic!("Guess Value must be less than 101, got {}", value);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "Guess Value must be less than 101")]
fn valid_guess() {
Guess::new(101);
}
#[test]
#[should_panic(expected = "Guess Value must be less than 101")]
fn valid_guess2() {
Guess::new(0);
}
}$ cargo test
running 2 tests
test tests::valid_guess - should panic ... ok
test tests::valid_guess2 - should panic ... FAILED
failures:
---- tests::valid_guess2 stdout ----
thread 'tests::valid_guess2' panicked at src\lib.rs:73:13:
Guess Value must be greater than 0, got 0
note: panic did not contain expected string
panic message: `"Guess Value must be greater than 0, got 0"`,
expected substring: `"Guess Value must be less than 101"`#[should_panic(expected = "message")]attribute is used to check if the test function panics with the expected message.
Returning Result Types
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 3 == 4 {
Ok(())
} else {
Err(String::from("2 plus 2 does not equal four"))
}
}
}
$ cargo test
running 1 test
test tests::it_works ... FAILED
failures:
---- tests::it_works stdout ----
Error: "2 plus 2 does not equal four"Showing Output
fn prints_and_return_10(a: i32) -> i32 {
println!("I got the value {}", a);
10
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn this_test_will_pass() {
let value = prints_and_return_10(4);
assert_eq!(10, value);
}
#[test]
fn this_test_will_fail() {
let value = prints_and_return_10(8);
assert_eq!(5, value)
}
}$ cargo test -- --show-output
running 2 tests
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED
failures:
---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'tests::this_test_will_fail' panicked at src\lib.rs:131:9:
assertion `left == right` failed
left: 5
right: 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceRunning specific tests
$ cargo test this_test_will_pass
running 1 test
test tests::this_test_will_pass ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00scargo test test_nameis used to run a specific test, which starts with the nametest_name.
Ignoring Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
#[ignore]
fn expensive_test() {
// code that takes an hour to run
}
}$ cargo test
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s#[ignore]attribute is used to ignore the test function.
Running Ignored Tests
$ cargo test -- --ignored
running 1 test
test tests::expensive_test ... ignored
test result: ok. 0 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00sTest Organization
- Unit tests are small and more focused, test one module in isolation and test private interfaces.
- Integration tests are larger and test the entire program, test the public interfaces that the library provides to other libraries.
- integration_test.rs
use adder;
fn it_adds_two(){
assert_eq!(adder::add(2, 2), 4);
}$ cargo test
running 1 test
test tests::internal ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests\integration_test.rs (target\debug\deps\integration_test-545fb9ae521fbad3.exe)
running 1 test
test it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s$ cargo test --test integration_test
running 1 test
test it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s-
cargo test --test test_nameis used to run a specific integration test. -
testsdirectory treats each file as a separate crate, (this leads unexpected behavior when usinguse super::*). -
multiple integration test files and you want to share common code, you can put the common code into a
tests/common.rsFile
- common.rs
- integration_test.rs
pub fn setup() {
// setup code
}
$ cargo test
running 1 test
test tests::internal ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests\common.rs (target\debug\deps\common-6493fd2ebc909690.exe)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests\integration_test.rs (target\debug\deps\integration_test-545fb9ae521fbad3.exe)
running 1 test
test it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s- Here
common.rsis treated like a integration test file(not intended).
To avoid this issue
- mod.rs
- integration_test.rs
pub fn setup() {
// setup code
}
$ cargo test
running 1 test
test tests::internal ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests\integration_test.rs (target\debug\deps\integration_test-545fb9ae521fbad3.exe)
running 1 test
test it_adds_two ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00scommondirectory is treated as a module not as test crate, so it doesn't run as a test.common/mod.rsallows to use the common code in the integration test file.
- integration_test.rs
use adder;
mod common;
#[test]
fn it_adds_two() {
common::setup();
assert_eq!(4, adder::add_two(2))
}
- this way the common code can be shared between multiple integration test files.
⚠️
- lib.rs creates a library crate.
- main.rs creates an binary crate.
Cannot test binary crates with the integration tests.