Mike Sperber
and Ismael Figueroa
This manual provides documentation for a Racket implementation of Quickcheck, a library that tests the specification of a program with randomly generated test inputs.
The original Quickcheck is a library for Haskell.
If you have not installed the package yet, run the following command from the command-line:
raco pkg install quickcheck
Note that this package will require Racket v6.0 or newer to install.
Alternatively, you may use the GUI package manager from DrRacket to install the package.
To use Quickcheck, you first write a specification for a program as a property in ordinary Racket code. For example, the following is a property that expresses the English specification “the string->number function produces numbers when given strings”:
Given such a property, we can run random tests using quickcheck :
Falsifiable, after 0 tests:
str = "\u0002\u0003\u0002"
You may have already guessed that this property will be easily falsified, since most strings do not actually parse as numbers.
Next, let’s write a property that we expect will actually succeed. For example, the following encodes the English specification “given two lists of integers, the result of append ing them will have the same length as the sum of the original list lengths”:
Testing the property reveals that it holds up:
OK, passed 100 tests.
(pointxy)))
Falsifiable, after 0 tests:
p1 = #(struct:point -4526 6529) p2 = #(struct:point 8436 4434)
Oops, there is a bug in the distance function. If you fix it, all the checks should pass.
procedure
( quickcheck prop)→void?
prop:testable?
Prints the result of the check, including a counterexample for the property if one was found.
OK, passed 100 tests.
an integer representing the number of tests run,
a list of ...,
either #t if the tests were successful, #f if the arguments were exhausted, or an instance of the result structure type on a test failure.
> ntests100
> (firststamps)'()
> ok?#t
Prints the result of the check, including a counterexample for the property if one was found.
The max-test field represents the maximum number of succeeding tests that will be run. The max-fail field represents the maximum number of tests that can be run with no result before the checker terminates.
The size field should be a function of one argument that produces the test size given the current test number. The print-every field should be a function that takes the test number and the generated arguments and is called for its side effect.
The quickcheck and quickcheck-results functions use a default config where the test count is 100, the test size for test n is (+ 3(quotient n2)), the max fail count is ten times the test count, and nothing is printed. The default config can be adjusted to run different numbers of tests with with-small-test-count , with-medium-test-count , and with-large-test-count .
syntax
( with-small-test-count body...)
OK, passed 100 tests.
syntax
( with-medium-test-count body...)
OK, passed 1000 tests.
syntax
( with-large-test-count body...)
OK, passed 10000 tests.
syntax
( with-test-count test-count-exprbody...)
OK, passed 42 tests.
The stamp field represents the labels that were relevant to this test execution. The arguments-list is a list of the values generated for checking this test case.
syntax
( property ([idgen/arb-expr]...)body0body...)
The ids are bound to the result of the given gen/arb-exprs inside the body expressions. The body expressions are used as the bodies of a predicate function that will be run with newly generated values from the specified generators or arbitraries.
#<property>
Values that can be tested are the following:
By default quickcheck simply displays whether the property check results in success or, in case of failure, it prints the arguments that falsify the property along with some other metadata. This is not helpful for (semi-) automatic testing as it is done using RackUnit. Fortunately it is possible to combine the best of both worlds.
syntax
( check-property prop)
For example, let us check the previous string->number-returns-number property:
--------------------
FAILURE
name: check-property
location: eval:20:0
params: '(#<property>)
ntest:0
stamps: ()
arguments:"str = \"\\u0002\\u0003\\u0002\""
Falsifiable
--------------------
syntax
( check-property/config configprop)
syntax
( check-property/quiet prop)
syntax
( add-property-check-info ((namevalue)...))
The purpose of this form is to improve error messages when a property fails. For instance, to compare whether two functions are observationally equivalent for arbitrary arguments, we can define the following form using add-property-check-info to state the expected and actual values inside the declaration of a property:
['actualactual]))
--------------------
FAILURE
name: check-property
location: eval:24:0
expected: 1
actual: -1
ntest:0
stamps: ()
arguments:"n = 0"
Falsifiable
--------------------
Crucially, every time the property is tested with a different set of arguments add-property-check-info overrides the previous data.
The proc value should be a function that accepts an integer representing the size of the value, a random number generator, and returns a value for testing.
procedure
( choose-integer lowerupper)→generator?
lower:integer?upper:integer?
procedure
( choose-real lowerupper)→generator?
lower:real?upper:real?
value
value
procedure
( choose-char lowerupper)→generator?
lower:char?upper:char?
procedure
( choose-list elem-gensize)→generator?
elem-gen:generator?size:integer?
procedure
( choose-vector elem-gensize)→generator?
elem-gen:generator?size:integer?
procedure
( choose-string char-gensize)→generator?
char-gen:generator?size:integer?
procedure
( choose-symbol char-gensize)→generator?
char-gen:generator?size:integer?
procedure
( choose-one-of opts)→generator?
procedure
( choose-mixed promises)→generator?
procedure
( choose-with-frequencies freqs)→generator?
procedure
( generator-unit val)→generator?
val:any/c
syntax
( bind-generators ([idval-expr]...)body)
If val-expr produces a generator? , the remaining code is threaded through generator-bind , and id will be bound to the value produced by the generator. Note that this delays evaluation until the generator is asked to produce a value.
;choose-foo creates a generator that produces a foo such that; foo-a is an integer?; foo-b is (or/c foo? #f)#f)])(fooab)))
procedure
( generator-bind genk)→generator?
gen:generator?
procedure
( generator-sequence gens)→generator?
procedure
( sized f)→generator?
value
procedure
( arbitrary gentrans)→arbitrary?
gen:generator?
value
value
value
value
value
value
value
procedure
( arbitrary-mixed pred+promises)→arbitrary?
procedure
( arbitrary-one-of eql?vals)→arbitrary?
procedure
( arbitrary-pair fstrst)→arbitrary?
fst:arbitrary?rst:arbitrary?
procedure
( arbitrary-list elem)→arbitrary?
elem:arbitrary?
procedure
( arbitrary-vector elem)→arbitrary?
elem:arbitrary?
procedure
( arbitrary-tuple elem...)→arbitrary?
elem:arbitrary?
procedure
accessorsconstructor:procedure?elem:arbitrary?
value
value
value
procedure
( arbitrary-procedure resultarg...)→arbitrary?
result:arbitrary?arg:arbitrary?
syntax
( ==> bool-exprprop)
If bool-expr is #t, equivalent to prop. Otherwise, produces a property that returns no result.
procedure
( label strtest)→generator?
str:string?test:testable?
syntax
( classify really?label-exprtestable-expr)
syntax
( trivial really?testable-expr)
procedure
( collect lbltest)→generator?
lbl:any/ctest:testable?
procedure
( random-generator? val)→boolean?
val:any/c
procedure
rand:random-generator?
procedure
( random-generator-split rand)
rand:random-generator?
procedure
( random-integer rglowhigh)→integer?
low:integer?high:integer?
procedure
( random-real rglowhigh)→real?
low:real?high:real?