TestCase - v2.0.0
Overview
This crate provides the #[test_case]
procedural macro attribute that generates multiple parametrized tests using one body with different input parameters.
A test is generated for each data set passed in the test_case
attribute.
Under the hood, all test cases that share the same body are grouped into a mod, giving clear and readable test results.
What's changed
New features
=> with |x: T| assert!(x)
custom inline test assertions=> using path::to::fn
custom fn test assertionsignore
andinconclusive
can be combined with other keywords=> it|is ...
syntax is a built-in (previously requiredhamcrest2
crate integration)- Simple test cases support returning
Result<(), _>
as#[test]
attribute does #50
Improvements
- Code refactoring
- Test function is kept in its original scope so it can be reused for non-test related code #77
- Improved test case name selection
Breaking changes
- Deprecation of
inconclusive
within test description string - it will no longer act like modifier keyword - Removal of
hamcrest2
integration (it
andis
are kept, complex assertions now have different syntax)
Custom inline test assertions
Now it is possible to pass the closure which will assert the test case.
#[test_case(1.0 => with |v: f64| assert!(v.is_infinite()))]
#[test_case(0.0 => with |v: f64| assert!(v.is_nan()))]
fn divide_by_zero_f64_with_lambda(input: f64) -> f64 {
input / 0.0f64
}
Custom fn test assertions
Not only the closure is possible but also you can point to the plain function:
Given:
fn assert_is_power_of_two(input: u64) {
assert!(input.is_power_of_two())
}
You can now assert:
#[test_case(1 => using assert_is_power_of_two)]
fn power_of_two_with_using(input: u64) -> u64 {
input
}
Not only that, you can also create a higher order function like this one:
fn wrapped_pretty_assert(expected: u64) -> impl Fn(u64) {
move |actual: u64| { pretty_assertions::assert_eq!(actual, expected) }
}
to achieve even better parameterized tests:
#[test_case(1 => using wrapped_pretty_assert(1))]
fn pretty_assertions_usage(input: u64) -> u64 {
input
}
ignore
and inconclusive
can be combined with other keywords
Sometimes you might want to temporarily disable one of the test cases without removing the testing assertion.
Thanks to this release now it is possible:
#[test_case(12 => ignore matches Ok(_))]
fn ignore_supported(_value: u64) -> Result<(), Box<dyn Error>> {
todo!()
}
=> it|is ...
syntax is a built-in
Previously test-case was integrated with hamcrest2
assertion crate.
This release breaks this dependency and provides an alternative solution:
#[test_case(1.0 => is equal_to 2.0 ; "eq1")]
#[test_case(1.0 => is greater_or_equal_than 1.0 ; "geq1")]
#[test_case(1.0 => is geq 1.0 ; "geq2")]
#[test_case(1.0 => is almost_equal_to 2.1 precision 0.15 ; "almost_eq1")]
#[test_case(1.0 => is almost 2.0 precision 0.01 ; "almost_eq2")]
fn complex_tests(input: f64) -> f64 {
input * 2.0
}
It also supports asserting paths:
#[test_case("Cargo.toml" => is existing_path)]
#[test_case("src/lib.rs" => is file)]
#[test_case("src/" => is dir ; "short_dir")]
#[test_case("src/" => is directory ; "long_dir")]
fn create_path(val: &str) -> std::path::PathBuf {
std::path::PathBuf::from(val)
}
As well as collections:
#[test_case(vec![1, 2, 3, 4] => it contains 1)]
#[test_case(vec![1, 2, 3, 4] => it contains_in_order [3, 4])]
fn contains_tests(items: Vec<u64>) -> Vec<u64> {
items
}
You can also use the combinators:
#[test_case(1.0 => is gt 0.0 and lt 5.0)]
#[test_case(0.3 => is (gt 0.0 and lt 1.0) or gt 1.2)]
#[test_case(0.7 => is (gt 0.0 and lt 1.0) or gt 1.2)]
fn combinators(v: f32) -> f32 {
v * 2.0
}
Note: The combinators are still a bit unpolished. Currently
test_case
only supports one type of combinator per group.
It means that if you want to combineand
withor
you need to use(
)
to explicitly mark the precedence.
However...
We hope that in the next version we will lift this restriction by providing a full precedence parsing.
Simple test cases support returning Result<(), _>
Finally, the test_case
macro now supports the Result return type just like the #[test]
attribute does.
It means the test case would fail if the function returns Err(...)
.
#[test_case(12)]
#[test_case(13)]
fn is_even(value: u64) -> Result<(), String> {
if value % 2 == 0 {
Ok(())
} else {
Err("is odd".to_string())
}
}