Skip to content

TestCase - v2.0.0

Compare
Choose a tag to compare
@frondeus frondeus released this 23 Feb 07:57
· 48 commits to master since this release

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 assertions
  • ignore and inconclusive can be combined with other keywords
  • => it|is ... syntax is a built-in (previously required hamcrest2 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 and is 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 combine and with or 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())
    }
}