3
3
:toclevels: 4
4
4
:source-highlighter: pygments
5
5
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.
8
7
9
8
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]
10
9
10
+ == Overview
11
11
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:
16
13
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
18
32
of interest:
19
33
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
22
38
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 .
25
41
26
- == Use
27
42
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
34
44
35
- == Build and Install
45
+ The documentation for the 5 opam packages https://c-cube.github.io/qcheck/[is available here].
36
46
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.
38
52
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
40
62
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`:
43
64
44
65
$ opam install qcheck-core
45
66
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
+
46
74
To build the library from source
47
75
48
76
$ make
49
77
78
+ Normally, for contributors, `opam pin https://github.com/c-cube/qcheck`
79
+ will pin the 5 opam packages from this repository.
80
+
50
81
51
82
== License
52
83
@@ -60,7 +91,7 @@ and type the following to load QCheck:
60
91
61
92
[source,OCaml]
62
93
----
63
- #require "qcheck";;
94
+ #require "qcheck-core ";;
64
95
----
65
96
66
97
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:
105
136
----
106
137
# QCheck.Test.check_exn test;;
107
138
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)
109
140
----
110
141
111
142
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.
113
144
114
145
115
146
Now, let's run the buggy test with a decent runner that will print the results
116
147
nicely (the exact output will change at each run, because of the random seed):
117
148
118
149
----
119
- # QCheck_runner.run_tests [test];;
150
+ # #require "qcheck-core.runner";;
151
+ # QCheck_base_runner.run_tests [test];;
152
+ random seed: 452768242
120
153
121
154
--- Failure --------------------------------------------------------------------
122
155
123
- Test my_buggy_test failed (10 shrink steps):
156
+ Test my_buggy_test failed (14 shrink steps):
124
157
125
158
[0; 1]
126
159
================================================================================
127
160
failure (1 tests failed, 0 tests errored, ran 1 tests)
128
161
- : int = 1
129
162
----
130
163
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
133
165
parameter `~verbose:true`.
134
166
135
167
136
-
137
168
=== Mirrors and Trees
138
169
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.
143
172
144
173
Let's see how to generate random trees:
145
174
@@ -178,7 +207,6 @@ let arbitrary_tree =
178
207
(shrink_tree b >|= fun b' -> node a b')
179
208
in
180
209
QCheck.make tree_gen ~print:print_tree ~shrink:shrink_tree;;
181
-
182
210
----
183
211
184
212
Here we write a generator of random trees, `tree_gen`, using
@@ -223,7 +251,7 @@ let test_buggy =
223
251
QCheck.Test.make ~name:"buggy_mirror" ~count:200
224
252
arbitrary_tree (fun t -> t = mirror_tree t);;
225
253
226
- QCheck_runner .run_tests [test_buggy];;
254
+ QCheck_base_runner .run_tests [test_buggy];;
227
255
----
228
256
229
257
This test fails with:
@@ -261,8 +289,59 @@ let test_mirror =
261
289
arbitrary_tree
262
290
(fun t -> List.rev (tree_infix t) = tree_infix (mirror_tree t));;
263
291
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);;
265
343
344
+ QCheck_base_runner.run_tests [test_buggy];;
266
345
----
267
346
268
347
@@ -282,27 +361,35 @@ let test_hd_tl =
282
361
assume (l <> []);
283
362
l = List.hd l :: List.tl l));;
284
363
285
- QCheck_runner .run_tests [test_hd_tl];;
364
+ QCheck_base_runner .run_tests [test_hd_tl];;
286
365
----
287
366
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
+
288
371
=== Long tests
289
372
290
373
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),
292
375
and a long one that might be more exhaustive (but whose running time makes it
293
376
impossible to run at each build). To that end, each test has a 'long' version.
294
377
In the long version of a test, the number of tests to run is multiplied by
295
378
the `~long_factor` argument of `QCheck.Test.make`.
296
379
380
+
297
381
=== Runners
298
382
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.
301
384
The easiest one is probably `run_tests`, but if you write your tests in
302
385
a separate executable you can also use `run_tests_main` which parses
303
386
command line arguments and exits with `0` in case of success,
304
387
or an error number otherwise.
305
388
389
+ The module `QCheck_runner` from the `qcheck` opam package is similar, and
390
+ includes compatibility with `OUnit`.
391
+
392
+
306
393
=== Integration within OUnit
307
394
308
395
https://github.com/gildor478/ounit[OUnit] is a popular unit-testing framework
@@ -330,11 +417,8 @@ let _ =
330
417
run_test_tt_main
331
418
("tests" >:::
332
419
List.map QCheck_ounit.to_ounit_test [passing; failing])
333
-
334
420
----
335
421
336
- NOTE: the package `qcheck` contains the module `QCheck_runner`
337
- which contains both custom runners and OUnit-based runners.
338
422
339
423
=== Integration within alcotest
340
424
@@ -357,7 +441,6 @@ let failing =
357
441
QCheck.(list small_int)
358
442
(fun l -> l = List.sort compare l);;
359
443
360
-
361
444
let () =
362
445
let suite =
363
446
List.map QCheck_alcotest.to_alcotest
@@ -366,13 +449,14 @@ let () =
366
449
Alcotest.run "my test" [
367
450
"suite", suite
368
451
]
369
-
370
452
----
371
453
454
+
372
455
=== 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.
376
460
377
461
[source, Reason]
378
462
----
@@ -408,9 +492,11 @@ describe("qcheck-rely", ({test}) => {
408
492
409
493
----
410
494
411
- === Deriver
412
495
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:
414
500
415
501
[source,OCaml]
416
502
----
@@ -421,26 +507,10 @@ type tree = Leaf of int | Node of tree * tree
421
507
See the according https://github.com/c-cube/qcheck/tree/master/src/ppx_deriving_qcheck/[README]
422
508
for more information and examples.
423
509
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
-
440
510
441
511
=== Usage from dune
442
512
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:
444
514
445
515
[source,OCaml]
446
516
----
@@ -450,26 +520,27 @@ let test =
450
520
QCheck.(list small_nat)
451
521
(fun l -> List.rev l = l)
452
522
453
- let _ = QCheck_runner .run_tests_main [test]
523
+ let _ = QCheck_base_runner .run_tests_main [test]
454
524
----
455
525
456
- with the following `dune` file:
526
+ with the following `dune` file (note the `qcheck-core.runner` sub-package) :
457
527
458
528
[source,lisp]
459
529
----
460
530
(test
461
531
(name test)
462
532
(modules test)
463
- (libraries qcheck)
533
+ (libraries qcheck-core qcheck-core.runner )
464
534
)
465
535
----
466
536
467
537
and run it with `dune exec ./test.exe` or `dune runtest`.
468
538
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.
469
542
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`:
473
544
474
545
[source,OCaml]
475
546
----
@@ -479,17 +550,16 @@ let test =
479
550
QCheck.(list small_nat)
480
551
(fun l -> List.rev l = l)
481
552
482
- let _ = QCheck_base_runner .run_tests_main [test]
553
+ let _ = QCheck_runner .run_tests_main [test]
483
554
----
484
555
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:
487
557
488
558
[source,lisp]
489
559
----
490
560
(test
491
561
(name test)
492
562
(modules test)
493
- (libraries qcheck-core qcheck-core.runner )
563
+ (libraries qcheck)
494
564
)
495
565
----
0 commit comments