• Stars
    star
    343
  • Rank 123,371 (Top 3 %)
  • Language
    OCaml
  • License
    BSD 2-Clause "Sim...
  • Created about 11 years ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

QuickCheck inspired property-based testing for OCaml.

QCheck

QuickCheck inspired property-based testing for OCaml, and combinators to generate random values to run tests on.

build

The documentation can be found here. This library spent some time in qtest, but is now standalone again!

To construct advanced random generators, the following libraries might be of interest:

Jan Midtgaard (@jmid) has a lecture about property-based testing that relies on QCheck.

Use

See the documentation. I also wrote a blog post that explains how to use it and some design choices; however, be warned that the API changed in lots of small ways (in the right direction, I hope) so the code will not work any more. An Introduction to the Library is an updated version of the blog post’s examples.

Build

$ make

You can use opam:

$ opam install qcheck

License

The code is now released under the BSD license.

An Introduction to the Library

First, let’s see a few tests. Let’s open a toplevel (e.g. utop) and type the following to load QCheck:

#require "qcheck";;
Note
alternatively, it is now possible to locally do: dune utop src to load qcheck.

List Reverse is Involutive

We write a random test for checking that List.rev (List.rev l) = l for any list l:

let test =
  QCheck.Test.make ~count:1000 ~name:"list_rev_is_involutive"
   QCheck.(list small_nat)
   (fun l -> List.rev (List.rev l) = l);;

(* we can check right now the property... *)
QCheck.Test.check_exn test;;

In the above example, we applied the combinator list to the random generator small_nat (ints between 0 and 100), to create a new generator of lists of random integers. These builtin generators come with printers and shrinkers which are handy for outputting and minimizing a counterexample when a test fails.

Consider the buggy property List.rev l = l:

let test =
  QCheck.Test.make ~count:1000 ~name:"my_buggy_test"
   QCheck.(list small_nat)
   (fun l -> List.rev l = l);;

When we run this test we are presented with a counterexample:

# QCheck.Test.check_exn test;;
Exception:
QCheck.Test.Test_fail ("my_buggy_test", ["[0; 1] (after 23 shrink steps)"]).

In this case QCheck found the minimal counterexample [0;1] to the property List.rev l = l and it spent 23 steps shrinking it.

Now, let’s run the buggy test with a decent runner that will print the results nicely (the exact output will change at each run, because of the random seed):

# QCheck_runner.run_tests [test];;

--- Failure --------------------------------------------------------------------

Test my_buggy_test failed (10 shrink steps):

[0; 1]
================================================================================
failure (1 tests failed, 0 tests errored, ran 1 tests)
- : int = 1

For an even nicer output QCheck_runner.run_tests also accepts an optional parameter ~verbose:true.

Mirrors and Trees

QCheck provides many useful combinators to write generators, especially for recursive types, algebraic types, and tuples.

Let’s see how to generate random trees:

type tree = Leaf of int | Node of tree * tree

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

let tree_gen = 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))]
    ));;

(* generate a few trees, just to check what they look like: *)
QCheck.Gen.generate ~n:20 tree_gen;;

let arbitrary_tree =
  let open QCheck.Iter in
  let rec print_tree = function
    | Leaf i -> "Leaf " ^ (string_of_int i)
    | Node (a,b) -> "Node (" ^ (print_tree a) ^ "," ^ (print_tree b) ^ ")"
  in
  let rec shrink_tree = function
    | Leaf i -> QCheck.Shrink.int i >|= leaf
    | Node (a,b) ->
      of_list [a;b]
      <+>
      (shrink_tree a >|= fun a' -> node a' b)
      <+>
      (shrink_tree b >|= fun b' -> node a b')
  in
  QCheck.make tree_gen ~print:print_tree ~shrink:shrink_tree;;

Here we write a generator of random trees, tree_gen, using the fix combinator. fix is sized (it is a function from int to a random generator; in particular for size 0 it returns only leaves). The sized combinator first generates a random size, and then applies its argument to this size.

Other combinators include monadic abstraction, lifting functions, generation of lists, arrays, and a choice function.

Then, we define arbitrary_tree, a tree QCheck.arbitrary value, which contains everything needed for testing on trees:

  • a random generator (mandatory), weighted with frequency to increase the chance of generating deep trees

  • a printer (optional), very useful for printing counterexamples

  • a shrinker (optional), very useful for trying to reduce big counterexamples to small counterexamples that are usually more easy to understand.

The above shrinker strategy is to

  • reduce the integer leaves, and

  • substitute an internal Node with either of its subtrees or by splicing in a recursively shrunk subtree.

A range of combinators in QCheck.Shrink and QCheck.Iter are available for building shrinking functions.

We can write a failing test using this generator to see the printer and shrinker in action:

let rec mirror_tree (t:tree) : tree = match t with
  | Leaf _ -> t
  | Node (a,b) -> node (mirror_tree b) (mirror_tree a);;

let test_buggy =
  QCheck.Test.make ~name:"buggy_mirror" ~count:200
    arbitrary_tree (fun t -> t = mirror_tree t);;

QCheck_runner.run_tests [test_buggy];;

This test fails with:

--- Failure --------------------------------------------------------------------

Test mirror_buggy failed (6 shrink steps):

Node (Leaf 0,Leaf 1)
================================================================================
failure (1 tests failed, 0 tests errored, ran 1 tests)
- : int = 1

With the (new found) understanding that mirroring a tree changes its structure, we can formulate another property that involves sequentializing its elements in a traversal:

let tree_infix (t:tree): int list =
  let rec aux acc t = match t with
    | Leaf i -> i :: acc
    | Node (a,b) ->
      aux (aux acc b) a
  in
  aux [] t;;

let test_mirror =
  QCheck.Test.make ~name:"mirror_tree" ~count:200
    arbitrary_tree
    (fun t -> List.rev (tree_infix t) = tree_infix (mirror_tree t));;

QCheck_runner.run_tests [test_mirror];;

Preconditions

The functions QCheck.assume and QCheck.(=⇒) can be used for tests with preconditions. For instance, List.hd l :: List.tl l = l only holds for non-empty lists. Without the precondition, the property is false and will even raise an exception in some cases.

let test_hd_tl =
  QCheck.(Test.make
    (list int) (fun l ->
      assume (l <> []);
      l = List.hd l :: List.tl l));;

QCheck_runner.run_tests [test_hd_tl];;

Long tests

It is often useful to have two version of a testsuite: a short one that runs reasonably fast (so that it is effectively run each time a projet is built), and a long one that might be more exhaustive (but whose running time makes it impossible to run at each build). To that end, each test has a 'long' version. In the long version of a test, the number of tests to run is multiplied by the ~long_factor argument of QCheck.Test.make.

Runners

The module QCheck_runner defines several functions to run tests, including compatibility with OUnit. The easiest one is probably run_tests, but if you write your tests in a separate executable you can also use run_tests_main which parses command line arguments and exits with 0 in case of success, or an error number otherwise.

Integration within OUnit

OUnit is a popular unit-testing framework for OCaml. QCheck provides a sub-library qcheck-ounit with some helpers, in QCheck_ounit, to convert its random tests into OUnit tests that can be part of a wider test-suite.

let passing =
  QCheck.Test.make ~count:1000
    ~name:"list_rev_is_involutive"
    QCheck.(list small_nat)
    (fun l -> List.rev (List.rev l) = l);;

let failing =
  QCheck.Test.make ~count:10
    ~name:"fail_sort_id"
    QCheck.(list small_nat)
    (fun l -> l = List.sort compare l);;

let _ =
  let open OUnit in
  run_test_tt_main
    ("tests" >:::
       List.map QCheck_ounit.to_ounit_test [passing; failing])
Note
the package qcheck contains the module QCheck_runner which contains both custom runners and OUnit-based runners.

Integration within alcotest

Alcotest is a simple and colorful test framework for OCaml. QCheck now provides a sub-library qcheck-alcotest to easily integrate into an alcotest test suite:

let passing =
  QCheck.Test.make ~count:1000
    ~name:"list_rev_is_involutive"
    QCheck.(list small_int)
    (fun l -> List.rev (List.rev l) = l);;

let failing =
  QCheck.Test.make ~count:10
    ~name:"fail_sort_id"
    QCheck.(list small_int)
    (fun l -> l = List.sort compare l);;


let () =
  let suite =
    List.map QCheck_alcotest.to_alcotest
      [ passing; failing]
  in
  Alcotest.run "my test" [
    "suite", suite
  ]

Integration within Rely

Rely is a Jest-inspire native reason testing framework. @reason-native/qcheck-rely is available via NPM and provides matchers for the easy use of qCheck within Rely.

open TestFramework;
open QCheckRely;

let {describe} = extendDescribe(QCheckRely.Matchers.matchers);

describe("qcheck-rely", ({test}) => {
  test("passing test", ({expect}) => {
    let passing =
      QCheck.Test.make(
        ~count=1000,
        ~name="list_rev_is_involutive",
        QCheck.(list(small_int)),
        l =>
        List.rev(List.rev(l)) == l
      );
    expect.ext.qCheckTest(passing);
    ();
  });
  test("failing test", ({expect}) => {
    let failing =
      QCheck.Test.make(
        ~count=10, ~name="fail_sort_id", QCheck.(list(small_int)), l =>
        l == List.sort(compare, l)
      );

    expect.ext.qCheckTest(failing);
    ();
  });
});

Deriver

A ppx_deriver is provided to derive QCheck generators from a type declaration.

type tree = Leaf of int | Node of tree * tree
[@@deriving qcheck]

See the according README for more information and examples.

Compatibility notes

Starting with 0.9, the library is split into several components:

  • qcheck-core depends only on unix and bytes. It contains the module QCheck and a QCheck_base_runner module with our custom runners.

  • qcheck-ounit provides an integration layer for OUnit

  • qcheck provides a compatibility API with older versions of qcheck, using both qcheck-core and qcheck-ounit. It provides QCheck_runner which is similar to older versions and contains both custom and Ounit-based runners.

  • qcheck-alcotest provides an integration layer with alcotest

Normally, for contributors, opam pin https://github.com/c-cube/qcheck will pin all these packages.

More Repositories

1

ocaml-containers

A lightweight, modular standard library extension, string library, and interfaces to various libraries (unix, threads, etc.) BSD license.
OCaml
485
star
2

datalog

An in-memory datalog implementation for OCaml.
Prolog
247
star
3

iter

Simple iterator abstract datatype, intended to iterate efficiently on collections while performing some transformations.
OCaml
104
star
4

tiny_httpd

Minimal HTTP server using good old threads + blocking IO, with a small request router.
OCaml
76
star
5

printbox

print nested boxes, lists, arrays, tables in several formats
OCaml
75
star
6

stimsym

[toy] A rewriting language similar to the core of Mathematica
OCaml
55
star
7

moonpool

Commodity thread pools and concurrency primitives for OCaml 5
OCaml
54
star
8

gen

Simple, efficient iterators for OCaml
OCaml
53
star
9

mc2

[research] A modular SMT solver in OCaml, based on mcSAT
SMT
39
star
10

oseq

Purely functional iterators compatible with standard `seq`.
OCaml
32
star
11

batsat

A (parametrized) Rust SAT solver originally based on MiniSat
Rust
31
star
12

lwt-pipe

[beta] A multi-consumer, multi-producers blocking queue and stream for Lwt
OCaml
31
star
13

calculon

Library for writing IRC bots in OCaml, a collection of plugins, and a dramatic robotic actor.
OCaml
28
star
14

ocaml-iostream

generic I/O streams of bytes
OCaml
28
star
15

cconv

[dead] combinators for type conversion (serialization/deserialization) to/from several formats. See this blog post (outdated): http://cedeela.fr/universal-serialization-and-deserialization.html
OCaml
27
star
16

linol

Wrapper around the OCaml `lsp` library to make it easier to write LSP servers
OCaml
27
star
17

ezcurl

A simple wrapper around OCurl.
OCaml
27
star
18

bare-ocaml

runtime library and code-generator for BARE (https://baremessages.org/)
OCaml
24
star
19

sidekick

A modular library for CDCL(T) SMT solvers, with [wip] proof generation.
SMT
24
star
20

smbc

Experimental model finder/SMT solver for functional programming.
OCaml
23
star
21

ocaml-trace

Common interface for tracing/instrumentation libraries in OCaml
OCaml
22
star
22

choice

Choice operator in OCaml, providing a backtracking monad
OCaml
22
star
23

spelll

fuzzy string searching, using Levenshtein automaton. Can be used for spell-checking.
OCaml
22
star
24

ocaml-avro

Runtime library and schema compiler for the Avro serialization format
OCaml
21
star
25

sqlite3_utils

[beta] High-level wrapper around ocaml-sqlite3
OCaml
18
star
26

olinq

LINQ-like combinators for manipulating collections of in-memory data
OCaml
17
star
27

maki

[beta] persistent memoization of computations, e.g. for repeatable tests and benchmarks
OCaml
17
star
28

ocaml-bigstring

Overlay over bigarrays of chars
OCaml
15
star
29

thread-local-storage

thread-local storage for OCaml
OCaml
15
star
30

fuseau

[alpha] lightweight fiber library for OCaml 5
OCaml
13
star
31

ocaml-minisat

OCaml bindings to Minisat
C++
13
star
32

ocaml-gnuplot

bindings to gnuplot (fork of https://bitbucket.org/ogu/gnuplot-ocaml/)
OCaml
13
star
33

seq

compatibility package for the standard OCaml iterator type
OCaml
12
star
34

quip

[wip] Proof format and checker for first-order and higher-order theorem provers
OCaml
12
star
35

batsat-ocaml

OCaml bindings for batsat (https://github.com/c-cube/batsat)
OCaml
11
star
36

trustee

[wip] A LCF-style kernel of trust intended for certified ATP and proof checking for FOL/HOL.
OCaml
11
star
37

ocaml-qbf

OCaml bindings to QBF solver(s)
C
10
star
38

ocabot

IRC bot for #ocaml@freenode
OCaml
10
star
39

jsonrpc2

[unfinished] Jsonrpc2 for OCaml, parametrized by the underlying IO.
OCaml
10
star
40

IKSML

[toy] industrial-strength implementation of the SKI calculus, using XML
8
star
41

thrifty

[wip] Reimplementation of thrift in OCaml
Thrift
8
star
42

hashset_benchs

Reproducing https://www.reddit.com/r/rust/comments/4dd5yl/rust_vs_f_hashset_benchmark/
Clojure
8
star
43

funarith

[wip] functorial library with classic algorithms for arithmetic
OCaml
8
star
44

ocaml-atomic

Compatibility package for the Atomic module
OCaml
8
star
45

indexed-set

OCaml implementation of indexed sets (inspired from Haskell's ixSet library)
OCaml
8
star
46

ty

[draft] dynamic representation of types
OCaml
7
star
47

zulip-log-viewer

Small website to serve zulip logs obtained from [zulip archive](https://github.com/zulip/zulip-archive)
OCaml
6
star
48

ocaml-twirp

OCaml implementation of Twirp using ocaml-protoc
OCaml
6
star
49

poc-modular-io

proof of concept for https://github.com/ocaml/RFCs/pull/19
OCaml
6
star
50

frog-utils

[frozen] Scheduling and running jobs on a shared computer, then analyse their output
OCaml
5
star
51

oasis-parser

Simple parser for _oasis files
OCaml
5
star
52

ocaml-chord

[unfinished] Chord DHT implementation in OCaml (not production-ready)
OCaml
5
star
53

rcontext

Per-request context, inspired from Go's
OCaml
5
star
54

space_camels

[toy] Tiny game built to try notty
OCaml
5
star
55

smtlib-utils

A parser and some utils for SMTLIB. For a fully compliant parser, see https://github.com/Gbury/dolmen/.
SMT
5
star
56

gemini-client

[toy] ocaml client for gemini
OCaml
4
star
57

containers-lwt

Utilities for Lwt
OCaml
4
star
58

bender

[toy] IRC bot in rust, for my private use
Rust
4
star
59

iterators_bench

[bench] benchmark of various iterator implementations
OCaml
4
star
60

playground

various little experiments
OCaml
4
star
61

ccbor

[WIP] CBOR codec for OCaml
OCaml
4
star
62

ocaml-ty

[clone] Fork of ocaml-ty by Grégoire Henry (original url https://gitorious.org/ocaml-ty/)
OCaml
4
star
63

containers-misc

[toy] Random stuff from containers, low quality, experimental
OCaml
4
star
64

neperien

[unfinished] structured, hierarchical log system for OCaml
OCaml
4
star
65

andes

[toy, wip] A chain with lots of lemmas. More specifically, a logic programming engine.
OCaml
4
star
66

logtk

[migrated to https://github.com/c-cube/zipperposition] Logic toolkit, designed primarily for first-order automated reasoning. It aims at providing basic types and algorithms (terms, unification, orderings, indexing, etc.) that can be factored out of several applications.
OCaml
4
star
67

vim-tptp

Vim syntax file for the TPTP logic format (http://www.cs.miami.edu/~tptp/)
Vim Script
4
star
68

paradox

[clone] model finder for first-order logic, from http://vlsicad.eecs.umich.edu/BK/Slots/cache/www.cs.chalmers.se/~koen/paradox/
Haskell
4
star
69

bencode_rpc

[toy] Remote Procedure Call with B-encode serialization and Lwt concurrency (probably not production ready).
OCaml
3
star
70

ldl

[TOY] loop-dee-loop, an event loop
OCaml
3
star
71

bender-ocaml

[dead] Write OCaml plugins for bender
OCaml
3
star
72

quip-book

Book for Quip, a proof format for first-order and higher-order theorem provers
SMT
3
star
73

ocaml-aig

[toy] And-Inverter Graph in OCaml
OCaml
2
star
74

tiny-httpd-moonpool-bench

experiment with tiny_httpd using moonpool as a scheduler
Makefile
2
star
75

tomato-chan

[wip] IRC bot.
OCaml
2
star
76

unif-visitor

Playground for visitors on terms
OCaml
2
star
77

ocaml-polynomials

[toy] Toy implementation of multi-variable polynomials over Z
OCaml
1
star
78

microsat

experiments around microsat
Rust
1
star
79

playground-k8s

just playing with minikube
OCaml
1
star
80

ppx_deriving_cconv

[deprecated] ppx_deriving instance for CConv encoders/decoders
OCaml
1
star
81

tip-parser

[obsolete] parser for https://github.com/tip-org/
OCaml
1
star
82

irky

[wip] IRC client for OCaml
OCaml
1
star
83

ocaml-irclog

parsing IRC logs as produced by irssi
OCaml
1
star
84

strictly_tally

Implementation of relative placement tabulation for dance competitions
Rust
1
star