Generators produce lists of concrete (ground) terms of a given type. They have
four objectives in the testing process:
\begin{itemize}
\item determine which elements of the type to instantiate,
\item provide a usable instance of the element (i.e. a ground term),
\item partition the set of values into finite subsets,
with each subset uniquely labelled by a natural number called the \emph{rank},
\item define an order in which to provide the elements of a given rank.
\end{itemize}
The meaning of the rank is dependent on the nature of the generated structure,
and there may be more than one acceptable definition of rank for a given data
type. For example, the rank of a tree may be the number of leaves, or the
height of the tree, or the number of interior nodes. The definition of the
rank needs to be clearly documented for each generator.
For data structures, the rank is the number of data elements or nodes. For
simple base types, the rank is 1; for more complicated base types such as
Double, a ranked generator may make sense. Any function from Rank (Int) to a
list of values of a type can be a generator in GenCheck compatible type systems
as long as it satisfies the ranking criteria.
Generators might be constructed to:
\begin{itemize}
\item {embodying domain specific knowledge
e.g.physical addresses or international names,}
\item {improve efficiency for complicated structures,}
\item {incorporate value based constraints,}
\item {use incremental construction from smaller structures.}
\end{itemize}
\begin{code}
module Test.GenCheck.Generator.Generator
( Generator
, generate
, genTake
, enumGenerator
, StandardGens(..)
, Testable(..)
, stdEnumGens
, enumGens
) where
import System.Random (StdGen)
import Data.List (genericTake)
import Test.GenCheck.Base.Base (Rank, Count)
import Test.GenCheck.Generator.Enumeration as
Enum(Enumeration, Enumerated(..), counter, getUnsafe,Label)
import Test.GenCheck.Generator.EnumStrat
\end{code}
The Generator type is defined to be a function from rank to a list of values.
Generators are not obliged to provide all possible values of a type, nor are
they obliged to provide only one occurence of the type in the list. Although
those are useful properties for a generator to have, in some cases the
flexibility is desirable, such as random value generators. Note that even
though the rank partitions the type into finite discrete subsets, there is no
restriction against duplicating values in the list, so the list may infinite.
Any function that satisfies the Generator type can be used with GenCheck
compatible systems, as long as they are total over non-negative ranks.
The generate function generates values of multiple ranks in a flat list.
\begin{code}
type Generator a = Rank -> [a]
generate :: Generator a -> [(Rank, Count)] -> [a]
generate g = concatMap $ uncurry (genTake g)
genTake :: Generator a -> Rank -> Count -> [a]
genTake g r n = genericTake n (g r)
\end{code}
Enumerated generators use an enumeration (Generator.Enumeration) and an
enumerative strategy (a type independent list of indices, Generator.EnumStrat)
to order the generated values by mapping the selection order over the
enumeration. The strategy is assumed to provide indices in the domain of the
enumeration, given the size of the enumeration at that rank.
\begin{code}
enumGenerator :: EnumStrat -> Enumeration c a -> Generator (c a)
enumGenerator strat e rnk = fmap (Enum.getUnsafe e rnk) (strat (Enum.counter e rnk))
\end{code}
Four enumerative strategies form the standard approach to generating test
values, and these generators are grouped into the StandardGens structure for
use in the test programs and test suite building functions.
The standard generators are:
\begin{definition}
\item[allGen] an exhaustive generator
\item[xtrmGen] extreme / boundary condition generator
\item[uniGen] picks a fixed number of uniformly separated elements
\item[randGen] random generator (infinitely many elements)
\end{definition}
The generators can be restricted to an interval of the total set of values
by providing a range of indices (low index, high index) into the enumeration.
A set of standard generators can be defined from the enumeration, (the random
generator requires an integer seed and uses the System.Random module) or
overridden with more efficient implementations. The StandardGens structure is
just a convenient way to pass them around.
stdGenList fills in the uniform spacing provides the standard ordering in a
list format.
\begin{code}
class Show a => Testable a where
stdTestGens :: StandardGens a
data StandardGens a = StdGens
{ genAll :: Generator a
, genXtrm :: Generator a
, genUni :: Int -> Generator a
, genRand :: StdGen -> Generator a
}
| UnrankedGen (Generator a)
enumGens :: Enumeration c Label -> StandardGens (c Label)
enumGens e = StdGens allGen xtrmGen uniGen randGen
where
allGen = enumGenerator exhaustG e
xtrmGen = enumGenerator extreme e
uniGen = \m' -> enumGenerator (uniform m') e
randGen = \s' -> enumGenerator (randG s') e
stdEnumGens :: Enumerated c => StandardGens (c Label)
stdEnumGens = enumGens enumeration
\end{code}