I have come across a few instances in my testing with QuickCheck when it would have simplified things to write my own modifiers in some cases, but I'm not exactly sure how one would do this. In particular, it would be helpful to know how to write a modifier for generators of lists and of numerics (such as Int
). I'm aware of NonEmptyList
, and Positive
and NonNegative
, that are already in the library, but in some instances it would have made my tests clearer if I could have specified something like a list that is not only NonEmpty, but also NonSingleton (so, it has at least 2 elements), or an Int
that is greater than 1, not just NonZero
or Positive
, or an Int(egral)
that is even/odd, etc.
How do you write a new modifier in QuickCheck
147 Views Asked by josiah At
1
There's plenty of way in which you can do that. Here's some examples.
Combinator function
You can write a combinator as a function. Here's one that generates non-singleton lists from any
Gen a
:This has the same type as the built-in
listOf
function, and can be used in the same way:Here I've taken advantage of
Gen a
being aMonad
so that I could write both the function and the property withdo
notation, but you can also write it using monadic combinators if you so prefer.The function simply generates two values,
x1
andx2
, as well as a listxs
of arbitrary size (which can be empty), and creates a list of all three. Sincex1
andx2
are guaranteed to be single values, the resulting list will have at least those two values.Filtering
Sometimes you just want to throw away a small subset of generated values. You can to that with the built-in
==>
combinator, here used directly in a property:While this property is tautological, it demonstrates that the predicate you place to the left of
==>
ensures that whatever runs on the right-hand side of==>
has passed the predicate.Existing monadic combinators
Since
Gen a
is aMonad
instance, you can also use existingMonad
,Applicative
, andFunctor
combinators. Here's one that turns any number inside of anyFunctor
into an even number:Notice that this works for any
Functor f
, not just forGen a
. Since, however,Gen a
is aFunctor
, you can still useevenInt
:The
arbitrary
function call here creates an unconstrainedInteger
value.evenInt
then makes it even by multiplying it by two.Arbitrary newtypes
You can also use
newtype
to create your own data containers, and then make themArbitrary
instances:This also enables you to implement
shrink
, if you need it.You can use the
newtype
in a property like this:The
Arbitrary
instance usesarbitrary
for the typea
to generate an unconstrained valuei
, then doubles it and adds one, thereby ensuring that the value is odd.Take a look at the QuickCheck documentation for many more built-in combinators. I particularly find
choose
,elements
,oneof
, andsuchThat
useful for expressing additional constraints.