How to write test functions in Rust
Overview
In the Rust programming language, we use test functions to test the non-test code in a program.
We can use a test function using three main steps:
- We include any data that we need for the respective code.
- We include and run the code for which we write the test case.
- We write the test by asserting the values or results that we expect.
Writing test cases can help us find if any part of the code is working or not.
To write a test function, it is necessary to add an attribute #[test] before the start of the test function. Rust creates a binary file for the piece of code that includes the #[test] annotation.
#[test]fn success(){//The body of the test function}
Creating test functions
To test the code, we can write multiple test cases. We can run a test case by using the following command:
cargo test
The cargo test command executes the test functions that we write.
Example
Let’s write our own test function with the help of an example project.
- We create a project using the following command:
cargo new project
The new command creates a new directory, project. Our project directory contains the main.rs file.
- We write our function in the
main.rsfile.
mod test;fn check_even_num(value:i32) -> bool {if value % 2 == 0{true}else{false}}
- We create a
test.rsfile to write the test function.
use crate::check_even_num;#[test]fn success(){assert!(check_even_num(4));}#[test]fn check_failure(){assert_eq!(check_even_num(3),false);}
- We add the module test in the
main.rsfile by adding the following line:
mod test;
- We run the following command in the root folder of our project:
cargo test
It shows the following output:
Compiling project v0.1.0 (/project)Finished test [unoptimized + debuginfo] target(s) in 0.50sRunning unittests src/main.rs (/project/target/debug/deps/project-04898232a9e03d49)running 2 teststest test::check_failure ... oktest test::success ... oktest result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
We can run the cargo test command below and hit the "Run" button to see the terminal.
[package] name = "project" version = "0.1.0" authors = ["Laraib"] [dependencies]
Explanation
In the main.rs file, mod test; is added to include the test file.
In the test.rs file, we do the following:
- Line 3: We use
#[test]to specify this is a test case i.e.,success()andcheck_failure. - Line 4: In the
success()function, we useassert(). This passes the test if a true value is passed to it. - Line 9: In the
check_failure()test case,assert_eq!()compares the two values and passes the test case if they match.
Testing panics and ignore attributes in test functions
We use the #[should_panic] attribute when we try to write a condition where a function should panic. One example is when we write a function for division, and we create a panic case when the user inputs zero value for the denominator.
In the scenario where we write multiple test cases, we use the #[ignore] attribute to exclude the cases we don’t want to execute.
We can understand this by looking at an example.
Example
pub fn nonZeroDivision(a: u32, b: u32) -> u32 {
if b == 0 {
panic!("Divide-by-zero error");
} else if a < b {
panic!("Divide result is zero");
}
a / b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide() {
assert_eq!(nonZeroDivision(10, 2), 5);
}
#[test]
#[should_panic]
fn test_any_panic() {
nonZeroDivision(1, 0);
}
#[test]
#[should_panic(expected = "Divide result is zero")]
fn test_specific_panic() {
nonZeroDivision(1, 10);
}
}Explanation
- Lines 1–6: We write a function to divide two values.
- Lines 18–21: We use the
should_panickeyword to throwpanicwhen the function getszeroas the denominator value. - Lines 24–27: We use the
[ignore]keyword before the test case. This will ignore the individual test case when running the test cases for the function.
Free Resources