#test #case #tests #unit #testing

macro dev test-case

Provides #[test_case(…)] procedural macro attribute for generating parametrized test cases easily

12 releases (5 stable)

2.0.0-rc1 Dec 19, 2021
1.2.1 Oct 28, 2021
1.2.0 Jun 27, 2021
1.1.0 Jan 10, 2021
0.3.1 Sep 27, 2019

#9 in Testing

Download history 21073/week @ 2021-09-26 21147/week @ 2021-10-03 20144/week @ 2021-10-10 19719/week @ 2021-10-17 23853/week @ 2021-10-24 19300/week @ 2021-10-31 19892/week @ 2021-11-07 19456/week @ 2021-11-14 20414/week @ 2021-11-21 20764/week @ 2021-11-28 24041/week @ 2021-12-05 19756/week @ 2021-12-12 14418/week @ 2021-12-19 12492/week @ 2021-12-26 18753/week @ 2022-01-02 22345/week @ 2022-01-09

68,719 downloads per month
Used in less than 121 crates

MIT license

48KB
866 lines

Crates.io Docs.rs MIT License Build Status Maintenance

Test Case

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

License

Licensed under of MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)

Contributing

Project roadmap is available at link. All contributions are welcome.

Recommended tools:

  • cargo readme - to regenerate README.md based on template and lib.rs comments
  • cargo insta - to review test snapshots
  • cargo edit - to add/remove dependencies
  • cargo fmt - to format code
  • cargo clippy - for all insights and tips
  • cargo fix - for fixing warnings

Dependencies

~285–710KB
~17K SLoC