- Everything we know is subject to bias
- Everything we build reflects these biases
Our code reflects our biases, our tests are often biased similarly
Don't write tests
Write expectations
- Have the machine generate random test cases
- Make beliefs explicit, force them to pay rent
This is called property testing
// this property is false, but perhaps
// not unreasonable to expect to be true
proptest! {
#[test]
fn mult_and_div(ref a in any::<usize>()) {
let result = (a * 5) / 5;
assert_eq!(result, a);
}
}
$ cargo test
test mult_and_div ... FAILED
Test failed: attempt to multiply with overflow;
minimal failing input: ref a = 3689348814741910324
test result: FAILED. 0 passed; 1 failed
$ cat proptest-regressions/main.txt
# Seeds for failure cases proptest has
# generated. It is automatically read
# and these particular cases re-run before
# any novel cases are generated.
# shrinks to ref a = 3689348814741910324
xs 4050946508 1278147119 4151624343 875310407
Wonderful for testing codecs, serialization, compression, or any set of operations that should retain equality.
proptest! {
#[test]
fn compress_roundtrip(ref s in ".*") {
let result = decompress(compress(s));
assert_eq!(result, s);
}
}
It's easy to generate more structured input, too
proptest! {
#[test]
fn parses_all_valid_dates(
ref s in "[0-9]{4}-[0-9]{2}-[0-9]{2}"
) {
parse_date(s).unwrap();
}
}
proptest! {
#[test]
fn doesnt_crash(
bit in 0usize..1_000_000,
page_sz_exponent in 0usize..30
) {
let page_sz = 1 << page_sz_exponent;
let mut bits = Bitfield::new(page_sz);
assert_eq!(bits.set(bit, true), Change::Changed);
assert_eq!(bits.get(bit), true);
}
}
- Isolate business logic from IO concerns
- Use
assert!
anddebug_assert!
on non-trivial things! this makes our "fuzzers" extremely effective - Try not to use
unwrap()
everywhere, at least useexpect("helpful message")
to speed up debugging - When propagating errors, include context that helps you get back to the root