I want to write test cases that depend on parameters. My test case should be executed for each parameter and I want to see whether it succeeds or fails for each parameter.
I'm used to writing things like that in Java:
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
How can I achieve something similar with Rust? Simple test cases are working fine, but there are cases where they are not enough.
#[test]
fn it_works() {
assert!(true);
}
Note: I want the parameters as flexible as possible, for example: Read them from a file, or use all files from a certain directory as input, etc. So a hardcoded macro might not be enough.
The built-in test framework does not support this; the most common approach used is to generate a test for each case using macros, like this:
macro_rules! fib_tests {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let (input, expected) = $value;
assert_eq!(expected, fib(input));
}
)*
}
}
fib_tests! {
fib_0: (0, 0),
fib_1: (1, 1),
fib_2: (2, 1),
fib_3: (3, 2),
fib_4: (4, 3),
fib_5: (5, 5),
fib_6: (6, 8),
}
This produces individual tests with names fib_0
, fib_1
, &c.
My rstest
crate mimics pytest
syntax and provides a lot of flexibility. A Fibonacci example can be very neat:
use rstest::rstest;
#[rstest]
#[case(0, 0)]
#[case(1, 1)]
#[case(2, 1)]
#[case(3, 2)]
#[case(4, 3)]
#[case(5, 5)]
#[case(6, 8)]
fn fibonacci_test(#[case] input: u32, #[case] expected: u32) {
assert_eq!(expected, fibonacci(input))
}
pub fn fibonacci(input: u32) -> u32 {
match input {
0 => 0,
1 => 1,
n => fibonacci(n - 2) + fibonacci(n - 1)
}
}
Output:
/home/michele/.cargo/bin/cargo test
Compiling fib_test v0.1.0 (file:///home/michele/learning/rust/fib_test)
Finished dev [unoptimized + debuginfo] target(s) in 0.92s
Running target/debug/deps/fib_test-56ca7b46190fda35
running 7 tests
test fibonacci_test::case_1 ... ok
test fibonacci_test::case_2 ... ok
test fibonacci_test::case_3 ... ok
test fibonacci_test::case_5 ... ok
test fibonacci_test::case_6 ... ok
test fibonacci_test::case_4 ... ok
test fibonacci_test::case_7 ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Every case is run as a single test case.
The syntax is simple and neat and, if you need, you can use any Rust expression as the value in the case
argument.
rstest
also supports generics and pytest
-like fixtures.
Don't forget to add rstest
to dev-dependencies
in Cargo.toml
.
Probably not quite what you've asked for, but by using TestResult::discard
with quickcheck you can test a function with a subset of a randomly generated input.
extern crate quickcheck;
use quickcheck::{TestResult, quickcheck};
fn fib(n: u32) -> u32 {
match n {
0 => 0,
1 => 1,
_ => fib(n - 1) + fib(n - 2),
}
}
fn main() {
fn prop(n: u32) -> TestResult {
if n > 6 {
TestResult::discard()
} else {
let x = fib(n);
let y = fib(n + 1);
let z = fib(n + 2);
let ow_is_ow = n != 0 || x == 0;
let one_is_one = n != 1 || x == 1;
TestResult::from_bool(x + y == z && ow_is_ow && one_is_one)
}
}
quickcheck(prop as fn(u32) -> TestResult);
}
I took the Fibonacci test from this Quickcheck tutorial.
P.S. And of course, even without macros and quickcheck you still can include the parameters in the test. "Keep it simple".
#[test]
fn test_fib() {
for &(x, y) in [(0, 0), (1, 1), (2, 1), (3, 2), (4, 3), (5, 5), (6, 8)].iter() {
assert_eq!(fib(x), y);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With