File Based Tests In Rust
Background
Have you ever had an assignment where you were give a bunch of files,
and had to input them to your rust program? Have you ever wished,
if only I could generate a test for each file that I was given.
Well there is! It’s something that you can do with the test-each
crate!
Getting Started
To get started, let’s set up a simple function. This will just check to see if the contents of the string is “one”, “two”, or “three”, but this could be extended to do anything with a string.
fn is_one_two_three(str: &str) -> bool {
match str {
"one" => true,
"two" => true,
"three" => true,
_ => false,
}
}
Now that we have that function, we could write unit testing in the traditional way, or we could make files to test for us! Let’s do that, I like that idea!
echo one >> test/one.txt
echo two >> test/two.txt
echo three >> test/three.txt
Now that we have our files, we can add the test-each crate.
cargo add test-each
Now lets make the tests:
#[cfg(test)]
mod file_tests {
use crate::is_one_two_three;
#[test_each::file(glob = "tests/*.txt", name(segments = 1))]
fn parse_file(src_file_contents: &str) {
assert!(is_one_two_three(src_file_contents.trim()))
}
}
Explanation
Here we can see the
#[test_each::file(glob = "tests/*.txt", name(segments = 1))]
. The
glob = "tests/*.txt"
tells the compiler to generate a test for every file
that matches the glob pattern. The name(segments = 1)
tells the compiler
what to name the functions. In this case, it will append the name of the file
to the test function name. This is just the surface though, check the
docs for more info on what else
you can do.
Also notice: the test function parameters. parse_file(src_file_contents: &str)
this means that it will, in fact, read the contents of the file, and pass it to
the function as src_file_contents
in this case. You could alternatively also
have a PathBuf parameter type and it would pass you the file path of the
testing file.
To run, its as simply as running cargo test
like normal testing!
Why This Crate Was Useful
This was useful to me during a project where I was writing a Logo interpreter; I had a test file for every feature of the language I was implementing. At first, I was writing tests to for every file to make sure the contents were properly handled, but I later found this crate to test files in bulk. Instead of writing a repetitive test for every file, all I did was write three functions:
- one for a parse error
- one for a runtime error
- one for a success
This vastly improved extensibility and encouraged me to write more test files, since the code to test the file was generated automatically.
Full Contents of Main
#[cfg(test)]
mod file_tests {
use crate::is_one_two_three;
#[test_each::file(glob = "tests/*.txt", name(segments = 1))]
fn parse_file(src_file_contents: &str) {
assert!(is_one_two_three(src_file_contents.trim()))
}
}
fn is_one_two_three(str: &str) -> bool {
match str {
"one" => true,
"two" => true,
"three" => true,
_ => false,
}
}
fn main() {}