Crate test_case[][src]

Expand description

Overview

test_case crate provides procedural macro attribute that generates parametrized test instances.

Getting Started

Crate has to be added as a dependency to Cargo.toml:

[dev-dependencies]
test-case = "2.0.0-rc1"

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;

Example usage:

#[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

Advanced use

For #[test_case(body)] the body is built as follows:

body := $arguments ($expected_result)? ($description)?

Arguments

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

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.

Modifiers

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

Validator

There are numerous validators provided by test_case:

validator := $simple|$matching|$panicking|$with|$using|$complex

Simple

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

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

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 := 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 := 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

complex := (it|is) $complex_expression

complex_expression := $cmp_assertion|$path_assertion|$collection_assertion

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.

Notes about async & additional attributes

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() { }

Attribute Macros

Generates tests for given set of data