1 stable release

Uses old Rust 2015

1.0.0 Dec 4, 2022

#9 in #shrinking

Download history 24/week @ 2024-06-24 21/week @ 2024-07-01 18/week @ 2024-07-08 45/week @ 2024-07-15 37/week @ 2024-07-22 24/week @ 2024-07-29 31/week @ 2024-08-05 19/week @ 2024-08-12 23/week @ 2024-08-19 22/week @ 2024-08-26 23/week @ 2024-09-02 69/week @ 2024-09-09 39/week @ 2024-09-16 59/week @ 2024-09-23 28/week @ 2024-09-30 32/week @ 2024-10-07

164 downloads per month
Used in 5 crates

Unlicense OR MIT

8KB
74 lines

quickcheck

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.

Documentation

The API is fully documented: https://docs.rs/qcheck.

The #[quickcheck] attribute

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

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.

Cranking the Number of Tests

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.

Generating Structs

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),
        }
    }
}

License

Dual-licensed under MIT or the UNLICENSE.

Dependencies

~1.5MB
~37K SLoC