A very important part of any software are the unit tests, after all, they help us verifying that the cases that are in our heads are indeed correctly implemented and also that whoever is the next lucky fellow changing our code in the future (it might be ourselves!) will feel confident that their change won't break the application.
Rust implements tests in the same file that you are implementing your functions. I will first show one example and later explain how it works.
Creating the Unit Test
First, let's see a piece of code I used in a tool to generate posts for my newly rewritten blog system Texted2
fn render_header(id: &str, name: &str, date: &str, title: Option<&str>) -> String {
let mut buf = String::new();
let _ = writeln!(&mut buf, "[ID]: # ({})", id);
let _ = writeln!(&mut buf, "[DATE]: # ({})", date);
let _ = writeln!(&mut buf, "[AUTHOR]: # ({})", name);
let _ = writeln!(&mut buf, "");
if let Some(title) = title {
let _ = writeln!(&mut buf, "# {}", title);
} else {
let _ = writeln!(&mut buf, "# Replace with title");
}
buf
}
Let's say I want to generate a unit test to verify this header is correctly generated.
In the file test_data.rs, I will create the expected post header
pub(crate) const HEADER_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)
# This is a title
"#;
And now I am going to write my unit test
fn render_header(id: &str, name: &str, date: &str, title: Option<&str>) -> String {
// ...
}
// First, note the test is in the same file
// Second, #[cfg(test)] tells the compiler that this code only is used then configuration = test
#[cfg(test)]
mod tests {
// import POST_DATA const
use test_data::POST_DATA;
// import everything defined in the file (super from mod tests)
// So, I can use render_header instead of super::render_header
use super::*;
// And now our unit test.
#[test]
fn test_happy_case() {
let id = "bcfc427f-f9f3-4442-bfc2-deca95db96d5";
let name = "Thiago";
let date = "2024-02-27 06:20:53.000";
let title = "This is a title";
let header = render_header(&id, &name, &date, Some(title));
assert_eq!(header, POST_DATA);
}
}
The attribute #[cfg()]
has many possible values, e.g.
- test -> Unit test
- target_os = "linux"
- target_arch = "aarch64"
The unit test is done.
XXX is never used
Quite certainly, functions and consts are going to be created specifically for some unit tests.
When building the application, you might face something quite annoying.
warning: constant `POST_DATA` is never used
--> src/bin/post-create/test_data.rs:2:7
|
2 | const POST_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)
| ^^^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
Basically it means that this is not being used as the unit tests are not being compiled (the compiler conditional #[cfg(test)]
is false when not building unit tests).
There are 2 simple ways to fix that.
- Add
#[allow(dead_code)]
before that const - Add
#[cfg(test)]
before that const (my preferred)
E.g.
#[cfg(test)]
pub(crate) const POST_DATA: &str = r#"[ID]: # (bcfc427f-f9f3-4442-bfc2-deca95db96d5)
# This is a title
"#;
Running tests
In the command line, run
cargo test
That is all
Link to the post in the author's blog:
https://thiagocafe.com/view/20240226_unit_tests_in_rust/
Top comments (1)
well