Generators and the 'choose' function

21 Feb 2015

Test data is produced, and distributed, by test data generators.

Both QuickCheck and FsCheck provide default generators for primitive types, but it’s also possible to build custom generators for custom types.

Generators have types of the form Gen a , where a is the type of the test data to be produced.

QuickCheck

Generators are built from the function choose , which makes a random choice of a value from a range, with a uniform distribution in the closed interval [a, a].

choose :: Random a => ( a , a ) -> Gen a

The type Gen is an instance of Haskell’s Monad . This involves implementing its minimal complete definition:

return :: a -> Gen a ( >>= ) :: Gen a -> ( a -> Gen b ) -> Gen b

Example: Take a random element from a list

Using do notation:

import Test.QuickCheck takeFromList :: [ a ] -> Gen a takeFromList xs = do i <- choose ( 0 , length xs - 1 ) return $ xs !! i

A possible translation into vanilla monadic code:

import Test.QuickCheck takeFromList :: [ a ] -> Gen a takeFromList xs = choose ( 0 , length xs - 1 ) >>= \ i -> return $ xs !! i

FsCheck

Similarly, FsCheck defines the type gen as a computation expression.

The choose function is non-generic; instead it generates a 32-bit signed integer, with a uniform distribution in the closed interval [l, h].

val choose : l : int * h : int -> Gen < int >

Example: Take a random element from a list

open FsCheck open FsCheck . Gen let takeFromList xs = gen { let ! i = choose ( 0 , List . length xs - 1 ) return xs |> Seq . nth i }

Notice that takeFromList has equivalent signature with the one in Haskell:

val takeFromList : xs : 'a list -> Gen < 'a > // F # takeFromList :: [ a ] -> Gen a // Haskell

References