fasttest.plt : FastTest Random Test Case Generation
_fasttest.plt : FastTest Random Test Case Generation_
Written by: Carl Eastlund and Carter Schonwald
Bug reports: Send to Carl Eastlund '(cce @ ccs . neu . edu)
Keywords: _quickcheck_ _randomized_ _testing_
This software is distributed under a BSD-style license (see license.txt).
================================================================================
FastTest is a library for random generation of test cases for unit testing. It
was inspired by the QuickCheck library for Haskell [1]. It includes two
libraries:
random.ss : Randomized generators for sets of data.
schemeunit.ss : SchemeUnit integration for testing based on generators.
[1] http://www.cs.chalmers.se/~rjmh/QuickCheck/
================================================================================
_random.ss : Random data generation_
This module provides tools for constructing random distributions of data. It
introduces one new type: (Generator T), representing a random generator that
produces values of type T. Any non-generator value may be used as a constant
generator which always produces that value.
The functions and macros provided by random.ss are described below. Examples
show valid expressions followed by representative values they may produce. Most
functions have two varieties: random-X, which provides a generator for some X,
and choose-X, which immediately generates an X.
In general, (choose-X ARG ...) = (generate (random-X ARG ...)).
--------------------
> (generator? v) : Boolean
v : Any
Reports whether v is an explicit generator (a generator constructed with the
functions in this module).
Remember that non-generator values may be used as constant generators. The
function nonrandom can be used to convert these to explicit generators.
Example: (generator? 'value) ;; #f
Example: (generator? (nonrandom 'value)) ;; #t
Example: (generator? (random-symbol)) ;; #t
--------------------
> (generate gen [pred count]) : T
gen : (Generator T)
pred : (-> T Boolean) default (lambda (v) #t)
count : Positive-Integer default (default-generate-attempts)
Constructs a random value from the given generator. Retries up to count times
until the value satisfies the given predicate; raises an error if no satisfying
value is found.
Example: (generate 'value) ;; 'value
Example: (generate (nonrandom 'value)) ;; 'value
Example: (generate (random-symbol)) ;; 'X, 'Y
Example: (generate (random-int-between 1 100) even? 10) ;; 2, 88, 94, 26
Example: (generate (random-string) char?) ;; error
--------------------
> default-generate-attempts : (Parameter Positive-Integer)
This parameter controls the default number of attempts made to generate a random
value satisfying a given property. The original value is 100.
--------------------
> (nonrandom v) : (Generator v)
v : Any
Generates the given value deterministically.
Example: (generate (nonrandom 'value)) ;; 'value
--------------------
> (random-int-between i j) : (Generator Nat)
> (choose-int-between i j) : Nat
i : Integer
j : Integer >= i
Generates a random integer chosen uniformly between i and j, inclusive.
(choose-int-between i j) = (generate (random-int-between i j))
Example: (choose-int-between 1 10) ;; 7, 3, 10, 9, 1, 8
--------------------
> (random-size [minimum average]) : (Generator Nat)
> (choose-size [minimum average]) : Nat
minimum [= 0] : Nat
average [= 4] : Positive-Int
Generates potentially unbounded, but usually small, natural numbers, often
useful for determining the size of other random data.
Produces a geometric distribution with expectation (+ minimum average). The
odds of producing each value (+ minimum k) for non-negative integer k are:
(* (expt (/ average (+ average 1)) k) (/ 1 (+ average 1)))
(choose-size minimum average) = (generate (random-size minimum average))
Example: (choose-size) ;; 0, 7, 1, 5, 13, 0
Example: (choose-size 10 2) ;; 12, 10, 14, 11
--------------------
> (random-boolean [prob]) : (Generator Boolean)
> (choose-boolean [prob]) : Boolean
prob [= 1/2] : Real-in[0,1]
Generates booleans, choosing #t with the given probability and #f otherwise.
Defaults to fair choice.
(choose-boolean prob) = (generate (random-boolean prob))
Example: (choose-boolean) ;; #t, #f
Example: (choose-boolean 1/4) ;; #f, #t, #f, #f
--------------------
> (random-char [code-gen]) : (Generator Char)
> (choose-char [code-gen]) : Char
code-gen [= (random-int-between (char->integer #\A) (char->integer #\Z))]
: (Generator Nat)
Generates characters whose code is chosen from code-gen.
(choose-char code-gen) = (generate (random-char code-gen))
Example: (choose-char) ;; #\G, #\M, #\N, #\B
Example: (choose-char (random-int-between 32 126)) ;; #\c, #\9, #\space, #\A
--------------------
> (random-group-of make elem-gen [len-gen]) : (Generator (Groupof T))
> (choose-group-of make elem-gen [len-gen]) : (Groupof T)
make : T ... -> (Groupof T)
elem-gen : (Generator T)
len-gen [= (random-size)] : (Generator Nat)
Generates a random collection of elements. It chooses a number of elements from
len-gen, generates each from elem-gen, and passes them to the constructor make.
Example: (choose-group-of list (random-symbol)) ;; '(X), '(A B C)
Example: (choose-group-of cons (random-boolean) (nonrandom 2))
;; '(#t . #t), '(#t . #f), '(#f . #t), '(#f . #f)
> (random-list-of elem-gen [len-gen]) : (Generator (Listof T))
> (choose-list-of elem-gen [len-gen]) : (Listof T)
> (random-vector-of elem-gen [len-gen]) : (Generator (Vectorof T))
> (choose-vector-of elem-gen [len-gen]) : (Vectorof T)
> (random-string [char-gen len-gen]) : (Generator String)
> (choose-string [char-gen len-gen]) : String
> (random-bytes [byte-gen len-gen]) : (Generator Bytes)
> (choose-bytes [byte-gen len-gen]) : Bytes
elem-gen : (Generator T)
char-gen [= (random-char)] : (Generator Char)
byte-gen [= (random-int-between 0 255)] : (Generator Byte)
len-gen [= (random-size)] : (Generator Nat)
Generators for lists, vectors, strings, and byte strings, equivalent to
random-group-of or choose-group-of with the added first argument of list,
vector, string, or bytes, respectively.
--------------------
> (random-apply f gen ...) : (Generator B)
> (choose-apply f gen ...) : B
f : A ... -> B
gen ... : (Generator A) ...
Generates the result of applying f to random inputs chosen from each gen.
Example: (choose-apply cons (random-char) (random-string))
;; '(#\c . "+8Y")
> (random-list gen ...) : (Generator (list T ...))
> (random-vector gen ...) : (Generator (vector T ...))
gen : (Generator T) ...
Generators for random heterogenous lists and vectors, equivalent to random-apply
with the added first argument of list or vector, respectively.
--------------------
> (random-symbol [name-gen]) : (Generator Symbol)
> (choose-symbol [name-gen]) : Symbol
name-gen [= (random-string (random-char) 1)] : (Generator String)
Generates random interned symbols whose names are chosen from name-gen.
Equivalent to (random-apply string->symbol name-gen).
Example: (choose-symbol) ;; 'X, 'Y, 'CAT, 'DOG
Example: (choose-symbol (nonrandom "value")) ;; 'value
--------------------
> (random-uniform gen1 ... genK) : (Generator (Or T1 ... TK))
> (choose-uniform gen1 ... genK) : (Or T1 ... TK)
gen1 ... genK : (Generator T1) ... (Generator TK)
Generates a value from a fairly chosen generator out of gen1 ... genK.
Example: (choose-uniform 'bat 'cat 'dog) ;; 'bat, 'cat, 'dog
--------------------
> (random-weighted weight1 gen1 ... weightK genK) : (Generator (Or T1 ... TK))
> (choose-weighted weight1 gen1 ... weightK genK) : (Or T1 ... TK)
weight1 ... weightK : Positive-Rational
gen1 ... genK : (Generator T1) ... (Generator TK)
> (random-weighted* (list pair1 ... pairK)) : (Generator (Or T1 ... TK))
> (choose-weighted* (list pair1 ... pairK)) : (Or T1 ... TK)
pair1 ... pairK = (cons weight1 gen1) ... (cons weight1 genK)
Generates a value from a generator chosen with the associated weight.
Example: (choose-weighted 1 'bat 2 'cat 3 'dog) ;; 'cat, 'dog, 'bat, 'cat, 'dog
(choose-weighted* '((1 . bat) (2 . cat) (3 . dog))) ;; as above
--------------------
> (random-recursive rec-gen [weight1 gen1] ... [weightK genK]) : (Generator T)
> (choose-recursive rec-gen [weight1 gen1] ... [weightK genK]) : T
weight1 ... weightK : Positive-Rational
gen1 ... genK : (Generator T)
This macro creates a generator for a recursive data structure whose clauses are
generated by gen1 ... genK with respective probabilities prob1 ... probK; the
recursive generator is in scope for gen1 ... genK under the name rec-gen.
(choose-recursive r [w g] ...) = (generate (random-recursive r [w g] ...))
Example:
(define-struct leaf () #f)
(define-struct node (left right) #f)
(choose-recursive tree-gen
[2 (nonrandom (make-leaf))]
[1 (random-apply make-node tree-gen tree-gen)])
;; Possible results:
;; (make-leaf),
;; (make-node (make-leaf) (make-leaf)),
;; (make-node (make-node (make-leaf) (make-leaf)) (make-leaf))
--------------------
> (random-function f) : (Generator (A ... -> B))
> (choose-function f) : A ... -> B
f : A ... -> (Generator B)
Generates a function that produces random outputs. For each set of inputs, the
resulting function will get the corresponding generator from f and produce a
value from it. The function will memoize these values to produce consistent
results for any specific set of inputs.
Examples:
(define f
(generate
(random-function
(lambda (n)
(random-list-of (random-boolean) (nonrandom n))))))
;; Notice how (f 5) always produces the same list of 5 booleans:
(f 5) ;; '(#f #f #t #f #t)
(f 2) ;; '(#t #t)
(f 5) ;; '(#f #f #t #f #t)
(f 3) ;; '(#t #f #t)
(f 5) ;; '(#f #f #t #f #t)
--------------------
> (define-generator (name arg ...) (weight gen) ...) :: Macro
name :: Identifier
arg :: Identifier
weight :: Expression[Non-Negative-Rational]
gen :: Expression[Generator]
Defines a potentially recursive generator function with the given clauses and weights.
Example:
(define-generator (random-sexp max-depth)
[4 (random-symbol)]
[(if (< max-depth 1) 0 1)
(random-apply cons (random-sexp (- max-depth 1))
(random-sexp (- max-depth 1)))])
(random-sexp 2) ;; 'X, '(A . B), '((I . J) . K), '((F . O) . (U . R))
--------------------
> (let*-random ((var gen [pred count]) ...) body ...) ;; [] means optional
var :: Identifier
gen :: Expression[Generator]
pred :: Expression[Boolean] default #t
count :: Expression[Positive-Integer] default (default-generate-attempts)
body :: Expression
Binds random values in an expression. Equivalent to:
(let* ((var (generate gen (lambda (var) pred) count)) ...) body ...)
Example:
(let*-random ((x (random-int-between 1 10))
(y (random-int-between 1 15) (> y x) 10))
(positive? (- y x)))
;; #t (rarely error)
================================================================================
_schemeunit.ss : Randomized SchemeUnit testing_
--------------------
> (check-randomly ((var gen [pred count]) ...) body ...) ;; [] means optional
This is equivalent to let*-random from random.ss (see above), except that it
annotates each binding with SchemeUnit's with-check-info form so they are
included in test case output.
> (test-randomly name count ((var gen [p c]) ...) body ...) ;; [] means optional
name :: Expression[String]
count :: Expression[Positive-Integer]
body :: Expression
var, gen, p, c :: see check-randomly and let*-random
Creates a SchemeUnit test suite which runs count times; each time creates random
bindings for each clause and evaluates the body expressions. The resulting test
suite is equivalent to:
(test-suite name
(test-case "i" ;; for each 0 <= i < count
(check-randomly ((var gen p c) ...) body ...))
...)
Example:
;; This tests whether list-ref produces a member of its list argument.
;; It generates a list of symbols of random length up to 100.
;; It generates a random index into the list, up to 99,
;; retrying up to 100 times to get an index under the list's length.
;; It then checks whether (list-ref elems index) is a member of elems.
(test-randomly "list-ref-produces-member" 100
([elems (random-list-of (random-symbol)
(random-int-between 1 100))]
[index (random-int-between 0 99)
(< index (length elems))
100])
(check memq (list-ref elems index) elems))