jeffh / Fox

Property Based Testing Library for Objective-C and Swift. QuickCheck for Apple's Platforms.
http://fox-testing.rtfd.org
Other
624 stars 30 forks source link

"Famous" value generators #26

Closed jeffh closed 9 years ago

jeffh commented 9 years ago

For more comprehensive coverage, it would be useful to provide famous value generators. These generators prioritize "famous" values (minimum/maximum values) at a higher probability than can be achieved through normal generation:

This can be accomplished with FOXFrequency with a 1-10% probability of a generating famous values.

Tests:

Header Docs:

Docs:

chadbrewbaker commented 9 years ago

I'm trying to wrap my head around the shrinking. Say I have GenArray(GenPositiveInteger)

The checker finds a failure of the property under test at [1,37, 9, 47].

How does it walk the rose tree when you have a composition of generators like that? Your Integer shrinking example was very well written by the way!

jeffh commented 9 years ago

Hey @chadbrewbaker, thanks for being interested! I take no credit since the design is from test.check :smiley:.

If you haven't read it already, there is some documentation on how Fox's shrinking works. Fox will walk the tree depth-first assuming the generated value fails.

The array shrinking is necessarily more complex than integers because it generates "permutations" of the generated array. A shrinking step (immediate children in a rose tree) for an array generator are

The shrinking step is applied recursively to cover all permutations. The internal complexity mostly comes from generating rose trees from the first step type, since each element's children of its rose tree needs to be replace the original value.

If you want details of the shrinking implementation, start at +[FOXRoseTree shrinkTreeFromRoseTrees:]. What I just described above can be seen +[FOXRoseTree sequenceByExpandingRoseTrees:]. Just by eye-ball, I'd say close to half of the RoseTree implementation is to cater to shrinking collections (arrays and tuples).

Of course, if you just build a generator on top of array or tuple generators, this shrinking behavior is "free".

chadbrewbaker commented 9 years ago

Ok. So with three composed generators f(g(h())), the outer generator f() is shrunk and the inner generator values g(h()) stay fixed?

jeffh commented 9 years ago

It depends on how the generators are implemented. Each generator can customize the shrinking behavior as needed. f can choose to shrink the tree produced by g. The same applies for g(h()).

This makes something like FOXGenMap and FOXMap different:

So you could create a generator that suppresses shrinking by:

// coding from the waist, may not actually compile
id<FOXGenerator> NoShrinking(id<FOXGenerator> generator) {
    return FOXGenMap(generator, ^FOXRoseTree *(FOXRoseTree *generatedTree) {
        return [[FOXRoseTree alloc] initWithValue:generatedTree.value];
    });
}