test_case
crate provides procedural macro attribute that generates parametrized test instances.
Crate has to be added as a dependency to Cargo.toml
:
[dev-dependencies]
test-case = "2.0.0-rc2"
and imported to the scope of a block where it's being called
(since attribute name collides with rust's built-in custom_test_frameworks
) via:
use test_case::test_case;
#[cfg(test)]
mod tests {
use test_case::test_case;
#[test_case(-2, -4 ; "when both operands are negative")]
#[test_case(2, 4 ; "when both operands are positive")]
#[test_case(4, 2 ; "when operands are swapped")]
fn multiplication_tests(x: i8, y: i8) {
let actual = (x * y).abs();
assert_eq!(8, actual)
}
}
Output from cargo test
for this example:
$ cargo test
running 4 tests
test tests::multiplication_tests::when_both_operands_are_negative ... ok
test tests::multiplication_tests::when_both_operands_are_positive ... ok
test tests::multiplication_tests::when_operands_are_swapped ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
For #[test_case(body)]
the body is built as follows:
body
:= $arguments ($expected_result)? ($description)?
arguments
:= $expr(,$expr)*(,)?
Comma separated list of one or more expressions, eg.:
#[test_case(a, b, c,)]
#[test_case(())]
#[test_case(a_method_that_produces_arg(1, 2, 3), "a string")]
expected_result
:= => ($modifier)* $validator
Optional part that provides assertions to instantiated tests.
When using expected_result
version of test_case
tested function must return a type
that can be matched with validator. Each validator description states how to ensure
that the type returned by function can be matched.
modifier
:= ignore | inconclusive
Both ignore
and inconclusive
keywords indicate that test case should be skipped. This is equivalent to using
#[ignore]
attribute on normal test. Eg.:
#[test_case(0.0 => ignore 0.0)] // not yet implemented
There are numerous validators provided by test_case
:
validator
:= $simple|$matching|$panicking|$with|$using|$complex
simple
:= $expr
Accepts any expression that evaluates to function return type and
compares it against whatever tested block returns via assert_eq
. Eg.:
#[test_case("2.0" => 2.0)]
fn parses_a_string(arg_in: &str) -> f64 {
body omitted...
}
matching
:= matches $pattern
A pattern following keyword matches
.
Result of a function is compared to pattern
via MatchExpression. Eg.:
#[test_case("2.0" => matches Ok(_))]
#[test_case("1.0" => matches Ok(v) if v == 1.0f64)]
#[test_case("abc" => matches Err(_))]
panicking
:= panics ($expr)?
Indicates that test instance should panic. Works identical to #[should_panic]
test attribute.
Optional expression after the keyword is treated like expected
in should_panic. Eg.:
#[test_case(0 => panics "division by zero")]
with
:= with $closure
Allows manual assertions of the result of testing function. Closure must indicate argument type and it has to be implicitly convertible from type returned by testing function. Eg.:
#[test_case(2.0 => 0.0)]
#[test_case(0.0 => with |i: f64| assert!(i.is_nan()))]
fn test_division(i: f64) -> f64 {
0.0 / i
}
using
:= using $path
Work similar to with
attribute, with the difference being that instead of a closure
it accepts path to a function that should validate result of the testing function. Eg.:
fn is_power_of_two(input: u64) {
assert!(input.is_power_of_two())
}
#[test_case(1 => using self::is_power_of_two)]
fn some_test(input: u64) -> u64 {
"body omitted..."
}
complex
:= (it|is) $complex_expression
complex_expression
:= not $complex_expression_inner | $complex_expression_inner (and $complex_expression_inner)* | $complex_expression_inner (or $complex_expression_inner)*
complex_expression_inner
:= $cmp_assertion|$path_assertion|$collection_assertion|\($complex_expression\)
cmp_assertion
:= $ord_assertion|$almost_eq_assertion
path_assertion
:= existing_path|file|dir|directory
collection_assertion
:= contains $expr|contains_in_order $expr
ord_assertion
:= (eq|equal_to|lt|less_than|gt|greater_than|leq|less_or_equal_than|geq|greater_or_equal_than) $expr
almost_eq_assertion
:= (almost_equal_to|almost) $expr precision $expr
Complex assertions are created as an extension to test_case
allowing for more flexibility in comparisons. Eg.:
#[test_case(args => is lt 2*3.14)]
fn take_piece_of_circle(...) -> f64 {
"body omitted..."
}
#[test_case(args => is existing_path)]
fn installation_created_path(...) -> PathBuf {
"body omitted..."
}
#[test_case(args => is almost_equal_to 2.0 precision 0.00001)]
fn some_volatile_computation(...) -> f64 {
"body omitted..."
}
#[test_case(args => it contains "Jack")]
fn list_of_users(...) -> Vec<String> {
"body omitted..."
}
#[test_case(args => it contains_in_order [1, 2, 3])]
fn sorts_asc(...) -> Vec<i32> {
"body omitted..."
}
it
and is
have equivalent interpretation. Both variants exist in order to make test cases easier to read.
complex assertions are WIP content, use at own discretion.
If test_case
is used with async
tests, eg. #[tokio::test]
, or user wants to pass other attributes to each
test instance then additional attributes have to be added past first occurrence of #[test_case]
. Eg.:
#[test_case(...)]
#[tokio::test]
#[allow(clippy::non_camel_case_types)]
async fn xyz() { }
Licensed under of MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
Project roadmap is available at link. All contributions are welcome.
Recommended tools:
cargo readme
- to regenerate README.md based on template and lib.rs commentscargo insta
- to review test snapshotscargo edit
- to add/remove dependenciescargo fmt
- to format codecargo clippy
- for all insights and tipscargo fix
- for fixing warnings