Rust testing, data generation and const asserts
Basic Rust test example
Luckily getting starting with testing Rust code is reasonably straightforward and no external libraries are needed. cargo test
will run all test code in a Rust project. Test code is identified with the code attributes #[cfg(test)]
and #[test]
. For the placement of the test code, units tests are added at the end of a source code file and integration tests can be placed in their own tests
directory.
// Minimal Rust tests examplefn double(x: u8) -> u8 {
x * 2
}#[cfg(test)]
mod tests {
// import all from the parent scope into this scope
use super::*; #[test]
fn test_double() {
assert_eq!(double(2), 4);
} #[test]
fn test_error_and_none() {
assert!("not a number".parse::<u32>().is_err());
assert_eq!("not a number".parse::<u32>().ok(), None);
assert_eq!("4".parse::<u32>(), Ok(4));
}
}$ cargo test
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Data generation
Using the right tools will help make your tests more effective. Below are data generation libraries that can increase the coverage of your tests.
Mock data
The fake-rs
library contains an extensive list of functions to generate mock data (Lorem, Name, Internet, HTTP, Company, Address, Date/Time).
use fake::{Faker};
use fake::locales::{EN, ZH_TW};assert!(test_person(FirstName(EN).fake(), LastName(EN).fake()));
assert!(test_ip_processing(IPv6(EN).fake(), MACAddress(EN).fake()));let hash_map: HashMap<u8, u32> = Faker.fake();
println!("HashMap {:?}", hash_map);
Regex and arbitrary input tests
proptest
tests that certain properties of rust code hold for arbitrary inputs and, if a failure is found, automatically finds the minimal test case to reproduce the problem.
proptest! {
#[test]
fn parses_all_valid_dates(s in "[0-9]{4}-[0-9]{2}-[0-9]{2}") {
parse_date(&s).unwrap();
}
}
Machine learning generated tests
The test-data-generation
library trains a model on sample data, and then can generate additional data matching the format of the samples. The library uses a Markov decision process to build a model of the data.
use test_data_generation::data_sample_parser::DataSampleParser;let mut dsp = DataSampleParser::new();
// train on data contained in a CSV file
dsp.analyze_csv_file("user_data.csv".to_string())?;// generate data from a trained model
println!(dsp.generate_record());
Add these libraries under your [dev-dependencies] in the Cargo.toml file
# Cargo.toml[dev-dependencies]
fake = "2.0"
test-data-generation = "0.1.1"
proptest = "0.9.5"
Debug asserts
debug_asserts
statements can check preconditions, postconditions and invariants in the body of your code. They are only included and executed when your code is run in debug mode and are removed for production builds.
fn double(x: u8) {
debug_assert!(x > 0, "Precondition - x must be positive");
let doubled = x * 2; debug_assert_eq!(doubled, x * 2);
return doubled;
}
Doc tests
Built into the compiler is the ability to run tests in documentation comments. By default cargo test
will execute tests in documentation and report failures
/// # Examples
///
/// ```
/// let x = 5;
/// assert_eq!(x, 5);
/// ```
Const asserts
static_assertions
ensure conditions are met for const functions and const data. The const asserts will prevent code from compiling if they don’t pass.
// static_assertions = "1.1.0"
#[macro_use]
extern crate static_assertions;const MY_CONFIG: Config = ConfigConfig {
foo: 10,
bar: 20,
};const_assert!(MY_CONFIG.bar > MY_CONFIG.foo);
Other cargo commands
Run only tests matching a pattern
cargo test test_double
Run tests not marked with the #[ignore]
attribute
cargo test -- --ignored
Watch the project and run cargo test after any code changes
cargo install cargo-watch
cargo watch -x test
Examples Tests
Need some examples? Take a look at the Rust standard library code via the link below.
https://github.com/rust-lang/rust/blob/master/library/alloc/src/collections/binary_heap.rs