QuickCheck is a way to do property based testing using randomly generated input. This crate comes with the ability to randomly generate and shrink integers, floats, tuples, booleans, lists, strings, options and results. All QuickCheck needs is a property function—it will then randomly generate inputs to that function and call the property for each set of inputs. If the property fails (whether by a runtime error like index out-of-bounds or by not satisfying your property), the inputs are "shrunk" to find a smaller counter-example.
The shrinking strategies for lists and numbers use a binary search to cover the input space quickly.
The API is fully documented: https://docs.rs/qcheck.
To make it easier to write QuickCheck tests, the #[quickcheck]
attribute
will convert a property function into a #[test]
function.
To use the #[quickcheck]
attribute, you must import the quickcheck
macro
from the quickcheck_macros
crate:
#[cfg(test)]
mod tests {
fn reverse<T: Clone>(xs: &[T]) -> Vec<T> {
let mut rev = vec!();
for x in xs {
rev.insert(0, x.clone())
}
rev
}
#[quickcheck]
fn double_reversal_is_identity(xs: Vec<isize>) -> bool {
xs == reverse(&reverse(&xs))
}
}
Shrinking is a crucial part of QuickCheck that simplifies counter-examples for your properties automatically. For example, if you erroneously defined a function for reversing vectors as: (my apologies for the contrived example)
fn reverse<T: Clone>(xs: &[T]) -> Vec<T> {
let mut rev = vec![];
for i in 1..xs.len() {
rev.insert(0, xs[i].clone())
}
rev
}
And a property to test that xs == reverse(reverse(xs))
:
fn prop(xs: Vec<isize>) -> bool {
xs == reverse(&reverse(&xs))
}
quickcheck(prop as fn(Vec<isize>) -> bool);
Then without shrinking, you might get a counter-example like:
[quickcheck] TEST FAILED. Arguments: ([-17, 13, -12, 17, -8, -10, 15, -19,
-19, -9, 11, -5, 1, 19, -16, 6])
Which is pretty mysterious. But with shrinking enabled, you're nearly guaranteed to get this counter-example every time:
[quickcheck] TEST FAILED. Arguments: ([0])
Which is going to be much easier to debug.
Another approach is to just ask quickcheck to run properties more
times. You can do this either via the
tests()
method, or via the QUICKCHECK_TESTS
environment variable.
This will cause quickcheck to run for a much longer time. Unlike,
the loop approach this will take a bounded amount of time, which
makes it more suitable for something like a release cycle that
wants to really hammer your software.
It is very simple to generate structs in QuickCheck. Consider the following
example, where the struct Point
is defined:
struct Point {
x: i32,
y: i32,
}
In order to generate a random Point
instance, you need to implement
the trait Arbitrary
for the struct Point
:
use qcheck::{Arbitrary, Gen};
impl Arbitrary for Point {
fn arbitrary(g: &mut Gen) -> Point {
Point {
x: i32::arbitrary(g),
y: i32::arbitrary(g),
}
}
}
Dual-licensed under MIT or the UNLICENSE.