Skip to content

Commit a15d5de

Browse files
authoredMar 11, 2025··
Merge pull request #338 from jmid/README-updates
README updates
2 parents 531314a + e86d909 commit a15d5de

File tree

2 files changed

+151
-79
lines changed

2 files changed

+151
-79
lines changed
 

‎CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
causing `asciidoc` to error
1111
- Add a note to `QCheck{,2.Gen}.small_int_corners` and `QCheck{,2}.Gen.graft_corners`
1212
about internal state, and fix a range of documentation reference warnings
13+
- Reorganize and polish the `README`, rewrite it to use `qcheck-core`, and add
14+
a `QCheck2` integrated shrinking example
1315

1416
## 0.24
1517

‎README.adoc

+149-79
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,81 @@
33
:toclevels: 4
44
:source-highlighter: pygments
55

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

98
image::https://github.com/c-cube/qcheck/actions/workflows/main.yml/badge.svg[alt="build", link=https://github.com/c-cube/qcheck/actions/workflows/main.yml]
109

10+
== Overview
1111

12-
The documentation can be found https://c-cube.github.io/qcheck/[here].
13-
This library spent some time in
14-
https://github.com/vincent-hugot/iTeML[qtest], but is now
15-
standalone again!
12+
`QCheck` consists of a collection of `opam` packages and extensions:
1613

17-
To construct advanced random generators, the following libraries might be
14+
- `qcheck-core` - provides the core property-based testing API and depends only
15+
on `unix` and `dune`.
16+
- `qcheck-ounit` - provides an integration layer for https://github.com/gildor478/ounit[`OUnit`]
17+
- `qcheck-alcotest` - provides an integration layer for https://github.com/mirage/alcotest[`alcotest`]
18+
- `qcheck` - provides a compatibility API with older versions of `qcheck`,
19+
using both `qcheck-core` and `qcheck-ounit`.
20+
- `ppx_deriving_qcheck` - provides a preprocessor to automatically derive
21+
generators
22+
23+
In addition, the https://github.com/ocaml-multicore/multicoretests[`multicoretests`]
24+
repository offers
25+
26+
- `qcheck-stm` - for running sequential and parallel model-based tests
27+
- `qcheck-lin` - for testing an API for sequential consistency
28+
- `qcheck-multicoretests-util` - a small library of utility extensions, such as
29+
properties with time outs
30+
31+
To construct advanced random generators, the following libraries might also be
1832
of interest:
1933

20-
- https://gitlab.inria.fr/fpottier/feat/[Feat]
21-
- @gasche's https://github.com/gasche/random-generator/[generator library]
34+
- https://gitlab.inria.fr/fpottier/feat/[`feat`] - a library for functional
35+
enumeration and sampling of algebraic data types
36+
- https://github.com/gasche/random-generator/[`random-generator`] - a library
37+
experimenting with APIs for random generation
2238

23-
Jan Midtgaard (@jmid) has http://janmidtgaard.dk/quickcheck/index.html[a lecture] about
24-
property-based testing that relies on QCheck.
39+
Earlier `qcheck` spent some time in https://github.com/vincent-hugot/iTeML[qtest],
40+
but was since made standalone again.
2541

26-
== Use
2742

28-
See the documentation. I also wrote
29-
https://cedeela.fr/quickcheck-for-ocaml[a blog post] that explains
30-
how to use it and some design choices; however, be warned that the API
31-
changed in lots of small ways (in the right direction, I hope) so the code
32-
will not work any more.
33-
<<examples>> is an updated version of the blog post's examples.
43+
== Documentation
3444

35-
== Build and Install
45+
The documentation for the 5 opam packages https://c-cube.github.io/qcheck/[is available here].
3646

37-
You can install qcheck via opam:
47+
The section <<examples>> below offer a brief introduction to the
48+
library. These examples are based on an earlier
49+
https://cedeela.fr/quickcheck-for-ocaml[blog post by Simon] that also
50+
discusses some design choices; however, be warned that the API changed
51+
since then, so the blog post code will not work as is.
3852

39-
$ opam install qcheck
53+
Jan's http://janmidtgaard.dk/quickcheck/index.html[course material on
54+
FP and property-based testing] also offers an introduction to QCheck.
55+
56+
The OCaml textbook from Cornell University also contains
57+
https://cs3110.github.io/textbook/chapters/correctness/randomized.html[a
58+
chapter about property-based testing with QCheck].
59+
60+
61+
== Build and Install
4062

41-
The `qcheck` package is offered for compatibility.
42-
For a bare-bones installation you can use the `qcheck-core` package:
63+
You can install QCheck via `opam`:
4364

4465
$ opam install qcheck-core
4566

67+
This provides a minimal installation without needless dependencies.
68+
69+
Install the bigger `qcheck` package instead for compatibility with qcheck.0.8
70+
and before:
71+
72+
$ opam install qcheck
73+
4674
To build the library from source
4775

4876
$ make
4977

78+
Normally, for contributors, `opam pin https://github.com/c-cube/qcheck`
79+
will pin the 5 opam packages from this repository.
80+
5081

5182
== License
5283

@@ -60,7 +91,7 @@ and type the following to load QCheck:
6091

6192
[source,OCaml]
6293
----
63-
#require "qcheck";;
94+
#require "qcheck-core";;
6495
----
6596

6697
NOTE: alternatively, it is now possible to locally do: `dune utop src`
@@ -105,41 +136,39 @@ When we run this test we are presented with a counterexample:
105136
----
106137
# QCheck.Test.check_exn test;;
107138
Exception:
108-
QCheck.Test.Test_fail ("my_buggy_test", ["[0; 1] (after 23 shrink steps)"]).
139+
test `my_buggy_test` failed on ≥ 1 cases: [0; 1] (after 11 shrink steps)
109140
----
110141

111142
In this case QCheck found the minimal counterexample `[0;1]` to the property
112-
`List.rev l = l` and it spent 23 steps shrinking it.
143+
`List.rev l = l` and it spent 11 steps shrinking it.
113144

114145

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

118149
----
119-
# QCheck_runner.run_tests [test];;
150+
# #require "qcheck-core.runner";;
151+
# QCheck_base_runner.run_tests [test];;
152+
random seed: 452768242
120153
121154
--- Failure --------------------------------------------------------------------
122155
123-
Test my_buggy_test failed (10 shrink steps):
156+
Test my_buggy_test failed (14 shrink steps):
124157
125158
[0; 1]
126159
================================================================================
127160
failure (1 tests failed, 0 tests errored, ran 1 tests)
128161
- : int = 1
129162
----
130163

131-
132-
For an even nicer output `QCheck_runner.run_tests` also accepts an optional
164+
For an even nicer output `QCheck_base_runner.run_tests` also accepts an optional
133165
parameter `~verbose:true`.
134166

135167

136-
137168
=== Mirrors and Trees
138169

139-
140-
`QCheck` provides many useful combinators to write
141-
generators, especially for recursive types, algebraic types,
142-
and tuples.
170+
`QCheck` provides many useful combinators to write generators, especially for
171+
recursive types, algebraic types, and tuples.
143172

144173
Let's see how to generate random trees:
145174

@@ -178,7 +207,6 @@ let arbitrary_tree =
178207
(shrink_tree b >|= fun b' -> node a b')
179208
in
180209
QCheck.make tree_gen ~print:print_tree ~shrink:shrink_tree;;
181-
182210
----
183211

184212
Here we write a generator of random trees, `tree_gen`, using
@@ -223,7 +251,7 @@ let test_buggy =
223251
QCheck.Test.make ~name:"buggy_mirror" ~count:200
224252
arbitrary_tree (fun t -> t = mirror_tree t);;
225253
226-
QCheck_runner.run_tests [test_buggy];;
254+
QCheck_base_runner.run_tests [test_buggy];;
227255
----
228256

229257
This test fails with:
@@ -261,8 +289,59 @@ let test_mirror =
261289
arbitrary_tree
262290
(fun t -> List.rev (tree_infix t) = tree_infix (mirror_tree t));;
263291
264-
QCheck_runner.run_tests [test_mirror];;
292+
QCheck_base_runner.run_tests [test_mirror];;
293+
----
294+
295+
296+
=== Integrated shrinking with `QCheck2`
297+
298+
You may have noticed the `shrink_tree` function above to reduce tree
299+
counterexamples. With the newer `QCheck2` module, this is not needed
300+
as shrinking is built into its generators.
301+
302+
For example, we can rewrite the above tree generator to `QCheck2` by just
303+
changing the `QCheck` occurrences to `QCheck2`:
304+
305+
[source,OCaml]
306+
----
307+
type tree = Leaf of int | Node of tree * tree
308+
309+
let leaf x = Leaf x
310+
let node x y = Node (x,y)
311+
312+
let tree_gen = QCheck2.Gen.(sized @@ fix
313+
(fun self n -> match n with
314+
| 0 -> map leaf nat
315+
| n ->
316+
frequency
317+
[1, map leaf nat;
318+
2, map2 node (self (n/2)) (self (n/2))]
319+
));;
320+
321+
(* generate a few trees with QCheck2, just to check what they look like: *)
322+
QCheck2.Gen.generate ~n:20 tree_gen;;
323+
----
324+
325+
326+
`QCheck2.Test.make` has a slightly different API than `QCheck.Test.make`,
327+
in that it accepts an optional `~print` argument and consumes generators
328+
directly built with `QCheck2.Gen`:
329+
330+
[source,OCaml]
331+
----
332+
let rec print_tree = function
333+
| Leaf i -> "Leaf " ^ (string_of_int i)
334+
| Node (a,b) -> "Node (" ^ (print_tree a) ^ "," ^ (print_tree b) ^ ")";;
335+
336+
let rec mirror_tree (t:tree) : tree = match t with
337+
| Leaf _ -> t
338+
| Node (a,b) -> node (mirror_tree b) (mirror_tree a);;
339+
340+
let test_buggy =
341+
QCheck2.Test.make ~name:"buggy_mirror" ~count:200 ~print:print_tree
342+
tree_gen (fun t -> t = mirror_tree t);;
265343
344+
QCheck_base_runner.run_tests [test_buggy];;
266345
----
267346

268347

@@ -282,27 +361,35 @@ let test_hd_tl =
282361
assume (l <> []);
283362
l = List.hd l :: List.tl l));;
284363
285-
QCheck_runner.run_tests [test_hd_tl];;
364+
QCheck_base_runner.run_tests [test_hd_tl];;
286365
----
287366

367+
By including a precondition QCheck will only run a property on input
368+
satisfying `assume`'s condition, potentially generating extra test inputs.
369+
370+
288371
=== Long tests
289372

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

380+
297381
=== Runners
298382

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

389+
The module `QCheck_runner` from the `qcheck` opam package is similar, and
390+
includes compatibility with `OUnit`.
391+
392+
306393
=== Integration within OUnit
307394

308395
https://github.com/gildor478/ounit[OUnit] is a popular unit-testing framework
@@ -330,11 +417,8 @@ let _ =
330417
run_test_tt_main
331418
("tests" >:::
332419
List.map QCheck_ounit.to_ounit_test [passing; failing])
333-
334420
----
335421

336-
NOTE: the package `qcheck` contains the module `QCheck_runner`
337-
which contains both custom runners and OUnit-based runners.
338422

339423
=== Integration within alcotest
340424

@@ -357,7 +441,6 @@ let failing =
357441
QCheck.(list small_int)
358442
(fun l -> l = List.sort compare l);;
359443
360-
361444
let () =
362445
let suite =
363446
List.map QCheck_alcotest.to_alcotest
@@ -366,13 +449,14 @@ let () =
366449
Alcotest.run "my test" [
367450
"suite", suite
368451
]
369-
370452
----
371453

454+
372455
=== Integration within Rely
373-
https://reason-native.com/docs/rely/[Rely] is a Jest-inspire native reason testing framework.
374-
@reason-native/qcheck-rely is available via NPM and provides matchers for the easy
375-
use of qCheck within Rely.
456+
457+
https://reason-native.com/docs/rely/[Rely] is a Jest-inspire native reason
458+
testing framework. @reason-native/qcheck-rely is available via NPM and provides
459+
matchers for the easy use of qCheck within Rely.
376460

377461
[source, Reason]
378462
----
@@ -408,9 +492,11 @@ describe("qcheck-rely", ({test}) => {
408492
409493
----
410494

411-
=== Deriver
412495

413-
A ppx_deriver is provided to derive QCheck generators from a type declaration.
496+
=== Deriving generators
497+
498+
The `ppx_deriving_qcheck` opam package provides a ppx_deriver to derive QCheck
499+
generators from a type declaration:
414500

415501
[source,OCaml]
416502
----
@@ -421,26 +507,10 @@ type tree = Leaf of int | Node of tree * tree
421507
See the according https://github.com/c-cube/qcheck/tree/master/src/ppx_deriving_qcheck/[README]
422508
for more information and examples.
423509

424-
=== Compatibility notes
425-
426-
Starting with 0.9, the library is split into several components:
427-
428-
- `qcheck-core` depends only on unix and bytes. It contains the module
429-
`QCheck` and a `QCheck_base_runner` module with our custom runners.
430-
- `qcheck-ounit` provides an integration layer for `OUnit`
431-
- `qcheck` provides a compatibility API with older versions of qcheck,
432-
using both `qcheck-core` and `qcheck-ounit`.
433-
It provides `QCheck_runner` which is similar to older versions and contains
434-
both custom and Ounit-based runners.
435-
- `qcheck-alcotest` provides an integration layer with `alcotest`
436-
437-
Normally, for contributors,
438-
`opam pin https://github.com/c-cube/qcheck` will pin all these packages.
439-
440510

441511
=== Usage from dune
442512

443-
We can use the buggy test from above using the `qcheck` opam package:
513+
We can use the buggy test from above using the `qcheck-core` opam package:
444514

445515
[source,OCaml]
446516
----
@@ -450,26 +520,27 @@ let test =
450520
QCheck.(list small_nat)
451521
(fun l -> List.rev l = l)
452522
453-
let _ = QCheck_runner.run_tests_main [test]
523+
let _ = QCheck_base_runner.run_tests_main [test]
454524
----
455525

456-
with the following `dune` file:
526+
with the following `dune` file (note the `qcheck-core.runner` sub-package):
457527

458528
[source,lisp]
459529
----
460530
(test
461531
(name test)
462532
(modules test)
463-
(libraries qcheck)
533+
(libraries qcheck-core qcheck-core.runner)
464534
)
465535
----
466536

467537
and run it with `dune exec ./test.exe` or `dune runtest`.
468538

539+
We recommend using the `qcheck-core` package as it has a minimal set of
540+
dependencies and also avoids problems with using
541+
`(implicit_transitive_deps false)` in dune.
469542

470-
To keep things minimal or if you are using `(implicit_transitive_deps false)`
471-
in dune, you may want to use the `qcheck-core` package instead. To do so,
472-
we have to adapt the last line of the example to use `QCheck_base_runner`:
543+
To instead use the `qcheck` opam package and its included `QCheck_runner`:
473544

474545
[source,OCaml]
475546
----
@@ -479,17 +550,16 @@ let test =
479550
QCheck.(list small_nat)
480551
(fun l -> List.rev l = l)
481552
482-
let _ = QCheck_base_runner.run_tests_main [test]
553+
let _ = QCheck_runner.run_tests_main [test]
483554
----
484555

485-
and adjust the `dune` file accordingly to use `qcheck-core` and its
486-
`qcheck-core.runner` sub-package:
556+
with the following `dune` file:
487557

488558
[source,lisp]
489559
----
490560
(test
491561
(name test)
492562
(modules test)
493-
(libraries qcheck-core qcheck-core.runner)
563+
(libraries qcheck)
494564
)
495565
----

0 commit comments

Comments
 (0)
Please sign in to comment.