quicktheories / QuickTheories

Property based testing for Java 8
Apache License 2.0
505 stars 51 forks source link

Dependent generators #32

Closed monga closed 6 years ago

monga commented 6 years ago

I found myself in need of a dependent generator: I want two variables and the value generated for the second depends on the value generated for the first.

I can filter the pairs with an assuming

qt().forAll(integers.allPositive(), integers.allPositive).assuming((i, j) -> j > i)

but this reduces the number of examples and it may force a "Gave up after finding only ... examples" error.

My solution was to build my own Gen

private class DepGen<T1,T2> implements Gen<Pair<T1,T2>>{
        private Gen<T1> baseGen;
        private Function<T1, Gen<T2>> dependency;

        public DepGen(Gen<T1> g1, Function<T1, Gen<T2>> f){
            this.baseGen = g1;
            this.dependency = f;
        }

        @Override
        public Pair<T1, T2> generate(RandomnessSource in) {
            int counter = 0;
            while (true) {
                try {
                    T1 base = this.baseGen.generate(in);
                    T2 dep = this.dependency.apply(base).generate(in);
                    return Pair.of(base, dep);
                } catch (Exception e) {
                    if (++counter == 1000) throw e;
                }
            }
        }
    }

Now I can write

private DepGen<Integer, Integer> myGen =
            new DepGen<>(integers().allPositive(),
            i -> Generate.range(i, Integer.MAX_VALUE));

Is there a more straightforward way to do this?

hcoles commented 6 years ago

There are two methods on Gen that allow a value to be mapped in a random way.

flatMap doesn't quite work in this scenario as you can't conveniently get a Gen of pairs.

    Gen<Integer> g1 = integers().allPositive()
        .flatMap(i -> Generate.range(i, Integer.MAX_VALUE));

But mutate works nicely, when it maps it makes a source of randomness available to the mapping function.

    Gen<Pair<Integer,Integer>> g2 = integers().allPositive()
        .mutate((i,rand) -> Pair.of(i, Generate.range(i, Integer.MAX_VALUE).generate(rand)));
monga commented 6 years ago

Thank you: I experimented with flatMap, but not with mutate (in fact in mind it was bound to "mutation" testing). It seems a clean solution.