Introduction
We know that Rust has a very interactive & intelligent compiler, who'll notify us about all the errors, and warnings (what we ignore). But it cannot notify us about the issues in our business logic. So we should write tests (that we don't want to, but we should!)
Examples
Let's write a simple test, of adding two numbers. And if we initialize a library crate instead of a binary crate, this is something we'll get by default.
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);
}
}
So we have a function that adds two numbers. Then we have a tests module, and inside we have a test it_works
we're storing the sum of two numbers in result and checking if result is equal to 4 or not. assert_eq! is a macro that checks equality for us.
Tests are generally written in a separate tests folder. We'll be getting the test results, by cargo test not cargo run
Finished test [unoptimized + debuginfo] target(s) in 0.14s
Running unittests src/lib.rs (target/debug/deps/simple_tests-2cf9939b509694d3)
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
Doc-tests simple_tests
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
So we only have 1 test, and it passed, no tests failed or ignored.
Now let's fail 1 test.
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 not_works() {
let result = add(2, 2);
assert_eq!(result, 5);
}
}
Added another test fn not_works.
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/simple_tests-2cf9939b509694d3)
running 2 tests
test tests::it_works ... ok
test tests::not_works ... FAILED
failures:
---- tests::not_works stdout ----
thread 'tests::not_works' panicked at src/lib.rs:18:9:
assertion `left == right` failed
left: 4
right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::not_works
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
And we have tests 1 passed, 1 failed.
In Rust the expected & actual values are called left & right, and that's what we have in the failure message.
Now we can also use panic! to make a test fail.
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 not_works() {
let result = add(2, 2);
assert_eq!(result, 5);
}
#[test]
fn panics() {
panic!("Test function panicked!");
}
}
Output:
Finished test [unoptimized + debuginfo] target(s) in 0.23s
Running unittests src/lib.rs (target/debug/deps/simple_tests-2cf9939b509694d3)
running 3 tests
test tests::it_works ... ok
test tests::panics ... FAILED
test tests::not_works ... FAILED
failures:
---- tests::panics stdout ----
thread 'tests::panics' panicked at src/lib.rs:23:9:
Test function panicked!
---- tests::not_works stdout ----
thread 'tests::not_works' panicked at src/lib.rs:18:9:
assertion `left == right` failed
left: 4
right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::not_works
tests::panics
test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
Conclusion
Now these tests are very simple. It adds two numbers, something we'll do a lot, but hopefully will never write a test for in a real business. Then we intentially create a panic!
We'll cover how real tests are written when we build some Rusty apps, and they're as different as different business logic. This is only an introduction on how to write tests. That's all for now, and Cheers!