Testable functions
A representation of functions for property testing, featuring
random generation, shrinking, and printing.
This package implements the core functionality.
Separate packages integrate it with existing testing frameworks.
Summary
This package defines a type of testable functions a :-> r
,
representing functions a -> r
.
-
To interpret a testable function into a function a -> r
,
use applyFun :: (a :-> r) -> a -> r
.
-
To pretty-print a testable function,
use show :: Show r => (a :-> r) -> String
.
-
To shrink a testable function, given a shrinker for r
,
use shrinkFun :: (r -> [r]) -> (a :-> r) -> [a :-> r]
.
-
To randomly generate a testable function a :-> r
,
apply a cogenerator of a
to a generator of r
.
Cogenerators can be defined using combinators from this library.
Cogenerators
The type of cogenerators of a
is Co Gen a r
,
where Gen
is QuickCheck's monad of random generators
and r
is an abstract parameter (it's really forall r. Co Gen a r
).
That type Co Gen a r
is literally defined as a type synonym of
Gen r -> Gen (a :-> r)
.
Given both a cogenerator c :: Co Gen a r
, and a generator g :: Gen r
,
we can construct the generator of testable functions c g :: Gen (a :-> r)
.
(Users can just think of Co Gen
as a whole,
even though the implementation defines a more general Co
which may be applied to any monad.
Similarly, the parameter r
can be ignored most of the time;
it matters to cogenerators of parameterized types.)
There are several combinators to define cogenerators,
covering the following scenarios.
Newtypes and embeddings
If we have a newtype A
around some old type B
, and we also have
a cogenerator of B
:
newtype A = MkA { unA :: B }
cogenB :: Co Gen B r
Then cogenEmbed
transforms cogenB
into a cogenerator of A
:
cogenEmbed "unA" unA cogenB :: Co Gen A r
This is actually not restricted to newtypes:
any "embedding" function A -> B
(here, unA
) can be used to convert a
Co Gen B r
to a Co Gen A r
.
(Yes, there is a contravariant functor hiding there.)
Note that cogenEmbed
expects a name for that function as a String
in its first argument, for pretty-printing.
Generic data types
To define a cogenerator of a type which is an instance of Generic
(from
GHC.Generics
), use cogenGeneric
. For example, consider this type:
data Small a = Zero | One a | Two a a
deriving Generic
The function cogenGeneric
takes a heterogeneous list of
cogenerators, one for each constructor of the generic type.
This is cs
in the example below.
The heterogeneous list is constructed using (:+)
to append
elements and ()
for the end of the list.
For constructors with multiple fields,
use (.)
to compose cogenerators for individual fields.
For nullary constructors, use id
as the "nullary cogenerator".
cogenSmall ::
forall a.
(forall r. Co Gen a r) ->
(forall r. Co Gen (Small a) r)
cogenSmall cogenA = cogenGeneric cs where
cs
= id -- Nullary cogenerator, for the constructor Zero
:+ cogenA -- Cogenerator of a, for the constructor One
:+ (cogenA . cogenA) -- A cogenerator of a, once for each field of the constructor Two
:+ () -- End of the list
Functions
To generate higher-order testable functions (a -> b) :-> r
,
we need a cogenerator of functions a -> b
,
which we can define using cogenFun
.
To a first approximation, the function cogenFun
transforms
a cogenerator of b
into a cogenerator of (a -> b)
, provided
a way to generate, shrink, and show a
.
This is actually generalized further by allowing one to provide
a way to generate, shrink, and show a representation a0
of a
,
which can be equal to a
in simple cases,
but this generalization makes it possible to generate
functions of arbitrarily high order.
Hence, to construct a cogenerator of a -> b
,
the function cogenFun
takes the following arguments, in this order:
Concrete a0
: a dictionary containing a shrinker and a pretty-printer of
representations a0
;
Gen (Maybe a0)
: a random generator of a0
, it must generate Nothing
once in a while (say with probability 1/5 if you have no clue);
a0 -> a
: a function from representations to actual values
(id
in simple cases);
forall r. Co Gen b r
: a cogenerator of b
.
References
-
Shrinking and showing functions,
by Koen Claessen, Haskell Symposium 2012.
This package extends that work with support for higher-order functions.
Other implementations based on that paper can be found in:
Internal module policy
Modules under Test.Fun.Internal
are not subject to any versioning policy.
Breaking changes may apply to them at any time.
If something in those modules seems useful, please report it or create a pull
request to export it from an external module.