package qcheck

  1. Overview
  2. Docs

Quickcheck inspired property-based testing

The library takes inspiration from Haskell's QuickCheck library. The rough idea is that the programer describes invariants that values of a certain type need to satisfy ("properties"), as functions from this type to bool. She also needs to desribe how to generate random values of the type, so that the property is tried and checked on a number of random instances.

This explains the organization of this module:

  • 'aarbitrary is used to describe how to generate random values, shrink them (make counter-examples as small as possible), print them, etc. Auxiliary modules such as Gen, Print, and Shrink can be used along with make to build one's own arbitrary instances.
  • Test is used to describe a single test, that is, a property of type 'a -> bool combined with an 'a arbitrary that is used to generate the test cases for this property. Optional parameters allow to specify the random generator state, number of instances to generate and test, etc.

Examples:

  • List.rev is involutive:
let test =
  QCheck.(Test.make ~count:1000
   (list int) (fun l -> List.rev (List.rev l) = l));;

QCheck.Test.run_exn test;;
  • Not all lists are sorted (false property that will fail. The 15 smallest counter-example lists will be printed):
let test = QCheck.(
  Test.make
    ~count:10_000 ~max_fail:3
    (list small_int)
    (fun l -> l = List.sort compare l));;
QCheck.Test.check_exn test;;
  • generate 20 random trees using Arbitrary.fix :
type tree = Leaf of int | Node of tree * tree

let leaf x = Leaf x
let node x y = Node (x,y)

let g = QCheck.Gen.(sized @@ fix
  (fun self n -> match n with
    | 0 -> map leaf nat
    | n ->
      frequency
        [1, map leaf nat;
         2, map2 node (self (n/2)) (self (n/2))]
    ))

Gen.generate ~n:20 g;;

More complex and powerful combinators can be found in Gabriel Scherer's Generator module. Its documentation can be found here.

val (==>) : bool -> bool -> bool

b1 ==> b2 is the logical implication b1 => b2 ie not b1 || b2 (except that it is strict and will interact better with Test.check_exn and the likes, because they will know the precondition was not satisfied.).

module Gen : sig ... end

Pretty printing

module Print : sig ... end
module Iter : sig ... end
module Shrink : sig ... end

Arbitrary

A value of type 'a arbitrary glues together a random generator, and optional functions for shrinking, printing, computing the size, etc. It is the "normal" way of describing how to generate values of a given type, to be then used in tests (see Test)

type 'a arbitrary = {
  1. gen : 'a Gen.t;
  2. print : ('a -> string) option;
    (*

    print values

    *)
  3. small : ('a -> int) option;
    (*

    size of example

    *)
  4. shrink : 'a Shrink.t option;
    (*

    shrink to smaller examples

    *)
  5. collect : ('a -> string) option;
    (*

    map value to tag, and group by tag

    *)
}

a value of type 'a arbitrary is an object with a method for generating random values of type 'a, and additional methods to compute the size of values, print them, and possibly shrink them into smaller counterexamples

NOTE the collect field is unstable and might be removed, or moved into Test.

val make : ?print:'a Print.t -> ?small:('a -> int) -> ?shrink:'a Shrink.t -> ?collect:('a -> string) -> 'a Gen.t -> 'a arbitrary

Builder for arbitrary. Default is to only have a generator, but other arguments can be added

val set_print : 'a Print.t -> 'a arbitrary -> 'a arbitrary
val set_small : ('a -> int) -> 'a arbitrary -> 'a arbitrary
val set_shrink : 'a Shrink.t -> 'a arbitrary -> 'a arbitrary
val set_collect : ('a -> string) -> 'a arbitrary -> 'a arbitrary
val choose : 'a arbitrary list -> 'a arbitrary

Choose among the given list of generators. The list must not be empty; if it is Invalid_argument is raised.

val unit : unit arbitrary

always generates (), obviously.

val bool : bool arbitrary

uniform boolean generator

val float : float arbitrary

generates regular floats (no nan and no infinities)

val pos_float : float arbitrary

positive float generator (no nan and no infinities)

val neg_float : float arbitrary

negative float generator (no nan and no infinities)

val int : int arbitrary

int generator. Uniformly distributed

val int_bound : int -> int arbitrary

int_bound n is uniform between 0 and n included

val int_range : int -> int -> int arbitrary

int_range a b is uniform between a and b included. b must be larger than a.

val (--) : int -> int -> int arbitrary

Synonym to int_range

val int32 : int32 arbitrary

int32 generator. Uniformly distributed

val int64 : int64 arbitrary

int generator. Uniformly distributed

val pos_int : int arbitrary

positive int generator. Uniformly distributed

val small_int : int arbitrary

positive int generator. The probability that a number is chosen is roughly an exponentially decreasing function of the number.

val small_int_corners : unit -> int arbitrary

As small_int, but each newly created generator starts with a list of corner cases before falling back on random generation.

val neg_int : int arbitrary

negative int generator. The distribution is similar to that of small_int, not of pos_int.

val char : char arbitrary

Uniformly distributed on all the chars (not just ascii or valid latin-1)

val printable_char : char arbitrary

uniformly distributed over a subset of chars

val numeral_char : char arbitrary

uniformy distributed over '0'..'9'

val string_gen_of_size : int Gen.t -> char Gen.t -> string arbitrary
val string_gen : char Gen.t -> string arbitrary

generates strings with a distribution of length of small_int

val string : string arbitrary

generates strings with a distribution of length of small_int and distribution of characters of char

val small_string : string arbitrary

Same as string but with a small length (that is, 0--10)

val string_of_size : int Gen.t -> string arbitrary

generates strings with distribution of characters if char

val printable_string : string arbitrary

generates strings with a distribution of length of small_int and distribution of characters of printable_char

val printable_string_of_size : int Gen.t -> string arbitrary

generates strings with distribution of characters of printable_char

val small_printable_string : string arbitrary
val numeral_string : string arbitrary

generates strings with a distribution of length of small_int and distribution of characters of numeral_char

val numeral_string_of_size : int Gen.t -> string arbitrary

generates strings with a distribution of characters of numeral_char

val list : 'a arbitrary -> 'a list arbitrary

generates lists with length generated by small_int

val list_of_size : int Gen.t -> 'a arbitrary -> 'a list arbitrary

generates lists with length from the given distribution

val array : 'a arbitrary -> 'a array arbitrary

generates arrays with length generated by small_int

val array_of_size : int Gen.t -> 'a arbitrary -> 'a array arbitrary

generates arrays with length from the given distribution

val pair : 'a arbitrary -> 'b arbitrary -> ('a * 'b) arbitrary

combines two generators into a generator of pairs

val triple : 'a arbitrary -> 'b arbitrary -> 'c arbitrary -> ('a * 'b * 'c) arbitrary

combines three generators into a generator of 3-uples

val option : 'a arbitrary -> 'a option arbitrary

choose between returning Some random value, or None

val fun1 : 'a arbitrary -> 'b arbitrary -> ('a -> 'b) arbitrary

generator of functions of arity 1. The functions are always pure and total functions:

  • when given the same argument (as decided by Pervasives.(=)), it returns the same value
  • it never does side effects, like printing or never raise exceptions etc. The functions generated are really printable.
val fun2 : 'a arbitrary -> 'b arbitrary -> 'c arbitrary -> ('a -> 'b -> 'c) arbitrary

generator of functions of arity 2. The remark about fun1 also apply here.

val oneofl : ?print:'a Print.t -> ?collect:('a -> string) -> 'a list -> 'a arbitrary

Pick an element randomly in the list

val oneofa : ?print:'a Print.t -> ?collect:('a -> string) -> 'a array -> 'a arbitrary

Pick an element randomly in the array

val oneof : 'a arbitrary list -> 'a arbitrary

Pick a generator among the list, randomly

val always : ?print:'a Print.t -> 'a -> 'a arbitrary

Always return the same element

val frequency : ?print:'a Print.t -> ?small:('a -> int) -> ?shrink:'a Shrink.t -> ?collect:('a -> string) -> (int * 'a arbitrary) list -> 'a arbitrary

Similar to oneof but with frequencies

val frequencyl : ?print:'a Print.t -> ?small:('a -> int) -> (int * 'a) list -> 'a arbitrary

Same as oneofl, but each element is paired with its frequency in the probability distribution (the higher, the more likely)

val frequencya : ?print:'a Print.t -> ?small:('a -> int) -> (int * 'a) array -> 'a arbitrary

Same as frequencyl, but with an array

val map : ?rev:('b -> 'a) -> ('a -> 'b) -> 'a arbitrary -> 'b arbitrary

map f a returns a new arbitrary instance that generates values using a#gen and then transforms them through f.

  • parameter rev

    if provided, maps values back to type 'a so that the printer, shrinker, etc. of a can be used. We assume f is monotonic in this case (that is, smaller inputs are transformed into smaller outputs).

val map_same_type : ('a -> 'a) -> 'a arbitrary -> 'a arbitrary

Specialization of map when the transformation preserves the type, which makes shrinker, printer, etc. still relevant

val map_keep_input : ?print:'b Print.t -> ?small:('b -> int) -> ('a -> 'b) -> 'a arbitrary -> ('a * 'b) arbitrary

map_keep_input f a generates random values from a, and maps them into values of type 'b using the function f, but it also keeps the original value. For shrinking, it is assumed that f is monotonic and that smaller input values will map into smaller values

  • parameter print

    optional printer for the f's output

Tests

module TestResult : sig ... end
module Test : sig ... end
OCaml

Innovation. Community. Security.