[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4. Generating test cases

The QuickCheck tool for Haskell uses type classes so that arbitrary values of various types may be generated behind the scenes. In SML, we need to be more explicit, but the same holds true in Haskell if we don't want the default generator (positive integers only, for example). The Gen module holds a wide range of tools for creating random values of various structured types and, yes, even functions!

 

We begin with the raw random number generator. The new function generates a seed based on the current time. The range function produces random integers between those in the given pair, inclusive. The generator is applicative, in the sense that it returns the new state of the random number generator.

 
type rand
val new : unit → rand
val range : int * int → rand → int * rand

The generator for a type takes a random number stream and produces a value of that type, along with the new state of the stream.

 
type 'a gen = rand → 'a * rand
type ('a, 'b) co = 'a → 'b gen → 'b gen

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.1 Random-value combinators

lift v is a generator that always produces the given value. select picks uniform randomly from the values in the vector, while choose picks uniform randomly from the generators in the vector, to produce a value. For example:

 
   Gen.choose #[Gen.lift 42, Gen.Int.int]

will return the number 42 with 50% probability, and a random integer otherwise (but recall that Gen.Int.int is biased toward zero and the extrema). The primed version pairs each generator with an integer weight to bias the choice (making it non-uniform).

 
val lift : 'a → 'a gen
val select : 'a vector → 'a gen
val choose : 'a gen vector → 'a gen
val choose' : (int * 'a gen) vector → 'a gen

The functions ending in L are the same, except they operate on lists instead of vectors.

 
val selectL : 'a list → 'a gen
val chooseL : 'a gen list → 'a gen
val chooseL' : (int * 'a gen) list → 'a gen

Here are some basic map and filtering functions over generators.

 
val filter : ('a → bool) → 'a gen → 'a gen
val zip : ('a gen * 'b gen) → ('a * 'b) gen
val zip3 : ('a gen * 'b gen * 'c gen) →
           ('a * 'b * 'c) gen
val zip4 : ('a gen * 'b gen * 'c gen * 'd gen) →
           ('a * 'b * 'c * 'd) gen
val map : ('a → 'b) → 'a gen → 'b gen
val map2 : ('a * 'b → 'c) → ('a gen * 'b gen) →
           'c gen
val map3 : ('a * 'b * 'c → 'd) →
           ('a gen * 'b gen * 'c gen) → 'd gen
val map4 : ('a * 'b * 'c * 'd → 'e) →
           ('a gen * 'b gen * 'c gen * 'd gen) →
           'e gen

flip is just like flipping a fair coin. With flip', the coin is biased by the pair of integers given: flip' (3,5) will choose true three-eights of the time, and false five-eights.

 
val flip : bool gen
val flip' : int * int → bool gen

These produce lists or optional values by consulting the boolean generator about when to produce the nil list or NONE.

 
val list : bool gen → 'a gen → 'a list gen
val option : bool gen → 'a gen → 'a option gen

The following function produces any kind of sequential collection type, you just provide the tabulate function as the first parameter. The integer generator then determines how many elements the collection will have.

 
val vector : (int * (int → 'a) → 'b) →
             int gen * 'a gen → 'b gen

Here is an example, showing how we can generate strings with vector:

 
    Gen.vector CharVector.tabulate
               (Gen.range(6,10), Gen.select #[#"a", #"b", #"c"])

Here is a sample of the strings it generated in one test:

 
 › "abbacccbbb" : CharVector.vector
 › "bccbaabacb" : CharVector.vector
 › "aacbbbaba" : CharVector.vector
 › "aabbaca" : CharVector.vector
 › "acaacbb" : CharVector.vector
 › "cbbbccab" : CharVector.vector
 › "bbcaccca" : CharVector.vector
 
val variant : (int,'b) co
val arrow : ('a, 'b) co * 'b gen → ('a → 'b) gen
val cobool : (bool, 'b) co
val colist : ('a, 'b) co → ('a list, 'b) co
val coopt : ('a, 'b) co → ('a option, 'b) co

These turn generators into a stream of values. You can limit them by a given integer, or just use the default maximum number of values from the Settings.

 
type stream
val start : rand → stream
val limit' : int → 'a gen → ('a,stream) reader
val limit : 'a gen → ('a,stream) reader

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.2 Basis types

In addition to the general combinators, practically all of the SML Basis types have associated generators in sub-structures. The following generators can be instantiated for whatever character and string types your implementation provides, such as Gen.WideText.charByType. For the default character and string types, however, these are found in the top-level of the Gen structure.

 
type char                                       
type string                                     
type substring                                  
val char : char gen                             
val charRange : char * char → char gen         
val charFrom : string → char gen               
val charByType : (char → bool) → char gen     
val string : (int gen * char gen) → string gen 
val substring : string gen → substring gen     
val cochar : (char, 'b) co                      
val costring : (string, 'b) co                  
val cosubstring : (substring, 'b) co            
 

The functions in Gen.Int (and Gen.Int32, Gen.IntInf, etc.) generate integers in various ranges. They can easily be instantiated for whatever integer types your implementation provides. They are biased so that zero, maxInt, and minInt (if they exist) are generated much more often than other integers.

 
eqtype int                              
val int : int gen                       
val pos : int gen                       
val neg : int gen                       
val nonpos : int gen                    
val nonneg : int gen                    
val coint : (int, 'b) co                
 

The functions generating unsigned words are in structures such as Gen.Word, Gen.Word8, Gen,Word32, etc., depending on your implementation.

 
eqtype word                             
val word : word gen                     
val coword : (word, 'b) co              
 

These are in Gen.Real structure. Currently, real numbers are generated from strings of (decimal) digits, rather than from bits. So some valid reals will never be generated. This may not be sufficient for testing numerical code.

 
type real                               
val real : real gen                     
val frac : real gen                     
val pos : real gen                      
val neg : real gen                      
val nonpos : real gen                   
val nonneg : real gen                   
val finite : real gen                   
 

Generate dates and times from Gen.DateTime. The dateFromYear function uses the given generator to produce the year, but then it comes up with a month, day, hour, minute, and second itself. A few days are more likely than others because we do not bother to generate the correct number of days based on the month. This makes May 1st more likely than May 2nd, because it could also have been generated as April 31st. (The Basis Date.date normalizes the dates though, so you will never see April 31st.)

 
val weekday : Date.weekday gen              
val month : Date.month gen                  
val dateFromYear : int gen → Date.date gen 
val time : Time.time gen                    

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.3 Recursive types

As pointed out in the QuickCheck paper, one needs to be careful when generating tree-structured data, due to the strong possibility of non-termination. To avoid this problem, make the generator a function of a decreasing integer parameter. When that parameter reaches zero, the only choice is to return a leaf.

 
datatype tree = Node of tree * tree | Leaf of int
fun gentree 0 = Gen.map Leaf Gen.Int.int
  | gentree n = 
    Gen.choose' #[(1,Gen.map Leaf Gen.Int.int),
                  (4,Gen.map Node (Gen.zip(gentree(n div 2),
                                           gentree(n div 2))))]

[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Chris League on April, 14 2008 using texi2html 1.78.