I want to thoroughly test an implementation of the intersection of two BTreeSet
s. I can write:
use self::proptest::prelude::*;
proptest! {
#[test]
fn intersect_this(s1: BTreeSet<i32>, s2: BTreeSet<i32>) {
// ...
}
}
But this has poor code coverage, because the code is specialized in some cases that random sets are unlikely to hit. One of the special cases is sets whose ranges of elements are almost disjoint (one set has values <= x, the other set has values >= x). In Python with hypothesis (in which I'm a little less of a newbie), I'd write:
from hypothesis import given
from hypothesis.strategies import builds, integers, sets
from typing import Set
def touching_ranges(elements: Set[int], split: int):
return {elt for elt in elements if elt < split}.union({split}), \
{elt for elt in elements if elt > split}.union({split})
@given(builds(touching_ranges, sets(integers()), integers()))
def test_touching_ranges(sets):
s1, s2 = sets
assert len(s1.intersection(s2)) == 1
In Rust, I got no further than stuffing everything inside the body:
#[test]
fn touching(mut s1: BTreeSet<i32>, split: i32) {
let mut s2 = s1.split_off(&split);
s1.insert(split);
s2.insert(split);
prop_assert_eq!(s1.intersection(&s2).count(), 1);
}
How can I keep the transformation of arbitrary values out of the test case body? I couldn't understand any of the code samples I found regarding strategies and Stack Overflow has few proptest-related questions.
There is a built-in
BTreeSetStrategy
inproptest
, so it is relatively straightforward:Some syntax here is not vanilla Rust so it may need further explanation:
proptest!
macro, the tests are normal Rust functions except they also have access to thein Strategy
syntax in order to generate inputs.prop_compose!
macro. Again, this is a normal Rust function except it can have two argument lists. The first argument list is the usual input; the second one can use thein Strategy
syntax and arguments from the first. The return type indicates the type of value being generated. In this case, a tuple of twoBTreeSet
s.Strategy
implementations for tuples, so a tuple of types that implementStrategy
is itself aStrategy
. That's why the functiontouching_ranges
can be used as one.