{"id":13726082,"url":"https://github.com/c-cube/qcheck","last_synced_at":"2025-05-15T20:07:17.162Z","repository":{"id":11035688,"uuid":"13369447","full_name":"c-cube/qcheck","owner":"c-cube","description":"QuickCheck inspired property-based testing for OCaml.","archived":false,"fork":false,"pushed_at":"2025-04-24T19:37:27.000Z","size":3371,"stargazers_count":372,"open_issues_count":50,"forks_count":42,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-05-15T17:11:24.891Z","etag":null,"topics":["alcotest","monadic-interface","ocaml","ounit","property-based-testing","quickcheck","random","random-generator","testing"],"latest_commit_sha":null,"homepage":"https://c-cube.github.io/qcheck/","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/c-cube.png","metadata":{"files":{"readme":"README.adoc","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-10-06T21:06:54.000Z","updated_at":"2025-04-24T19:37:32.000Z","dependencies_parsed_at":"2023-12-07T09:35:22.306Z","dependency_job_id":"c6a76cb1-a16b-4057-af08-6953fee373cc","html_url":"https://github.com/c-cube/qcheck","commit_stats":null,"previous_names":[],"tags_count":38,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-cube%2Fqcheck","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-cube%2Fqcheck/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-cube%2Fqcheck/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/c-cube%2Fqcheck/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/c-cube","download_url":"https://codeload.github.com/c-cube/qcheck/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254414499,"owners_count":22067272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alcotest","monadic-interface","ocaml","ounit","property-based-testing","quickcheck","random","random-generator","testing"],"created_at":"2024-08-03T01:02:52.031Z","updated_at":"2025-05-15T20:07:12.042Z","avatar_url":"https://github.com/c-cube.png","language":"OCaml","readme":"= QCheck\n:toc: macro\n:toclevels: 4\n:source-highlighter: pygments\n\nQuickCheck inspired property-based testing for OCaml.\n\nimage::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]\n\n== Overview\n\n`QCheck` consists of a collection of `opam` packages and extensions:\n\n- `qcheck-core` - provides the core property-based testing API and depends only\n  on `unix` and `dune`.\n- `qcheck-ounit` - provides an integration layer for https://github.com/gildor478/ounit[`OUnit`]\n- `qcheck-alcotest` - provides an integration layer for https://github.com/mirage/alcotest[`alcotest`]\n- `qcheck` - provides a compatibility API with older versions of `qcheck`,\n  using both `qcheck-core` and `qcheck-ounit`.\n- `ppx_deriving_qcheck` - provides a preprocessor to automatically derive\n   generators\n\nIn addition, the https://github.com/ocaml-multicore/multicoretests[`multicoretests`]\nrepository offers\n\n- `qcheck-stm` - for running sequential and parallel model-based tests\n- `qcheck-lin` - for testing an API for sequential consistency\n- `qcheck-multicoretests-util` - a small library of utility extensions, such as\n  properties with time outs\n\nTo construct advanced random generators, the following libraries might also be\nof interest:\n\n- https://gitlab.inria.fr/fpottier/feat/[`feat`] - a library for functional\n  enumeration and sampling of algebraic data types\n- https://github.com/gasche/random-generator/[`random-generator`] - a library\n  experimenting with APIs for random generation\n\nEarlier `qcheck` spent some time in https://github.com/vincent-hugot/iTeML[qtest],\nbut was since made standalone again.\n\n\n== Documentation\n\nThe documentation for the 5 opam packages https://c-cube.github.io/qcheck/[is available here].\n\nThe section \u003c\u003cexamples\u003e\u003e below offer a brief introduction to the\nlibrary. These examples are based on an earlier\nhttps://cedeela.fr/quickcheck-for-ocaml[blog post by Simon] that also\ndiscusses some design choices; however, be warned that the API changed\nsince then, so the blog post code will not work as is.\n\nJan's http://janmidtgaard.dk/quickcheck/index.html[course material on\n FP and property-based testing] also offers an introduction to QCheck.\n\nThe OCaml textbook from Cornell University also contains\nhttps://cs3110.github.io/textbook/chapters/correctness/randomized.html[a\nchapter about property-based testing with QCheck].\n\n\n== Build and Install\n\nYou can install QCheck via `opam`:\n\n    $ opam install qcheck-core\n\nThis provides a minimal installation without needless dependencies.\n\nInstall the bigger `qcheck` package instead for compatibility with qcheck.0.8\nand before:\n\n    $ opam install qcheck\n\nTo build the library from source\n\n    $ make\n\nNormally, for contributors, `opam pin https://github.com/c-cube/qcheck`\nwill pin the 5 opam packages from this repository.\n\n\n== License\n\nThe code is now released under the BSD license.\n\n[[examples]]\n== An Introduction to the Library\n\nFirst, let's see a few tests. Let's open a toplevel (e.g. utop)\nand type the following to load QCheck:\n\n[source,OCaml]\n----\n#require \"qcheck-core\";;\n----\n\nNOTE: alternatively, it is now possible to locally do: `dune utop src`\nto load `qcheck`.\n\n=== List Reverse is Involutive\n\nWe write a random test for checking that `List.rev (List.rev l) = l` for\nany list `l`:\n\n[source,OCaml]\n----\nlet test =\n  QCheck.Test.make ~count:1000 ~name:\"list_rev_is_involutive\"\n   QCheck.(list small_nat)\n   (fun l -\u003e List.rev (List.rev l) = l);;\n\n(* we can check right now the property... *)\nQCheck.Test.check_exn test;;\n----\n\n\nIn the above example, we applied the combinator `list` to\nthe random generator `small_nat` (ints between 0 and 100), to create a\nnew generator of lists of random integers. These builtin generators\ncome with printers and shrinkers which are handy for outputting and\nminimizing a counterexample when a test fails.\n\nConsider the buggy property `List.rev l = l`:\n\n[source,OCaml]\n----\nlet test =\n  QCheck.Test.make ~count:1000 ~name:\"my_buggy_test\"\n   QCheck.(list small_nat)\n   (fun l -\u003e List.rev l = l);;\n----\n\nWhen we run this test we are presented with a counterexample:\n\n[source,OCaml]\n----\n# QCheck.Test.check_exn test;;\nException:\ntest `my_buggy_test` failed on ≥ 1 cases: [0; 1] (after 11 shrink steps)\n----\n\nIn this case QCheck found the minimal counterexample `[0;1]` to the property\n`List.rev l = l` and it spent 11 steps shrinking it.\n\n\nNow, let's run the buggy test with a decent runner that will print the results\nnicely (the exact output will change at each run, because of the random seed):\n\n----\n# #require \"qcheck-core.runner\";;\n# QCheck_base_runner.run_tests [test];;\nrandom seed: 452768242\n\n--- Failure --------------------------------------------------------------------\n\nTest my_buggy_test failed (14 shrink steps):\n\n[0; 1]\n================================================================================\nfailure (1 tests failed, 0 tests errored, ran 1 tests)\n- : int = 1\n----\n\nFor an even nicer output `QCheck_base_runner.run_tests` also accepts an optional\nparameter `~verbose:true`.\n\n\n=== Mirrors and Trees\n\n`QCheck` provides many useful combinators to write generators, especially for\nrecursive types, algebraic types, and tuples.\n\nLet's see how to generate random trees:\n\n[source,OCaml]\n----\ntype tree = Leaf of int | Node of tree * tree\n\nlet leaf x = Leaf x\nlet node x y = Node (x,y)\n\nlet tree_gen = QCheck.Gen.(sized @@ fix\n  (fun self n -\u003e match n with\n    | 0 -\u003e map leaf nat\n    | n -\u003e\n      frequency\n        [1, map leaf nat;\n         2, map2 node (self (n/2)) (self (n/2))]\n    ));;\n\n(* generate a few trees, just to check what they look like: *)\nQCheck.Gen.generate ~n:20 tree_gen;;\n\nlet arbitrary_tree =\n  let open QCheck.Iter in\n  let rec print_tree = function\n    | Leaf i -\u003e \"Leaf \" ^ (string_of_int i)\n    | Node (a,b) -\u003e \"Node (\" ^ (print_tree a) ^ \",\" ^ (print_tree b) ^ \")\"\n  in\n  let rec shrink_tree = function\n    | Leaf i -\u003e QCheck.Shrink.int i \u003e|= leaf\n    | Node (a,b) -\u003e\n      of_list [a;b]\n      \u003c+\u003e\n      (shrink_tree a \u003e|= fun a' -\u003e node a' b)\n      \u003c+\u003e\n      (shrink_tree b \u003e|= fun b' -\u003e node a b')\n  in\n  QCheck.make tree_gen ~print:print_tree ~shrink:shrink_tree;;\n----\n\nHere we write a generator of random trees, `tree_gen`, using\nthe `fix` combinator. `fix` is *sized* (it is a function from `int` to\na random generator; in particular for size 0 it returns only leaves).\nThe `sized` combinator first generates a random size, and then applies\nits argument to this size.\n\nOther combinators include monadic abstraction, lifting functions,\ngeneration of lists, arrays, and a choice function.\n\nThen, we define `arbitrary_tree`, a `tree QCheck.arbitrary` value, which\ncontains everything needed for testing on trees:\n\n- a random generator (mandatory), weighted with `frequency` to\n  increase the chance of generating deep trees\n- a printer (optional), very useful for printing counterexamples\n- a *shrinker* (optional), very useful for trying to reduce big\n  counterexamples to small counterexamples that are usually\n  more easy to understand.\n\nThe above shrinker strategy is to\n\n- reduce the integer leaves, and\n- substitute an internal `Node` with either of its subtrees or\n  by splicing in a recursively shrunk subtree.\n\nA range of combinators in `QCheck.Shrink` and `QCheck.Iter` are available\nfor building shrinking functions.\n\n\nWe can write a failing test using this generator to see the\nprinter and shrinker in action:\n\n[source,OCaml]\n----\nlet rec mirror_tree (t:tree) : tree = match t with\n  | Leaf _ -\u003e t\n  | Node (a,b) -\u003e node (mirror_tree b) (mirror_tree a);;\n\nlet test_buggy =\n  QCheck.Test.make ~name:\"buggy_mirror\" ~count:200\n    arbitrary_tree (fun t -\u003e t = mirror_tree t);;\n\nQCheck_base_runner.run_tests [test_buggy];;\n----\n\nThis test fails with:\n\n[source,OCaml]\n----\n\n--- Failure --------------------------------------------------------------------\n\nTest mirror_buggy failed (6 shrink steps):\n\nNode (Leaf 0,Leaf 1)\n================================================================================\nfailure (1 tests failed, 0 tests errored, ran 1 tests)\n- : int = 1\n----\n\n\nWith the (new found) understanding that mirroring a tree\nchanges its structure, we can formulate another property\nthat involves sequentializing its elements in a traversal:\n\n[source,OCaml]\n----\nlet tree_infix (t:tree): int list =\n  let rec aux acc t = match t with\n    | Leaf i -\u003e i :: acc\n    | Node (a,b) -\u003e\n      aux (aux acc b) a\n  in\n  aux [] t;;\n\nlet test_mirror =\n  QCheck.Test.make ~name:\"mirror_tree\" ~count:200\n    arbitrary_tree\n    (fun t -\u003e List.rev (tree_infix t) = tree_infix (mirror_tree t));;\n\nQCheck_base_runner.run_tests [test_mirror];;\n----\n\n\n=== Integrated shrinking with `QCheck2`\n\nYou may have noticed the `shrink_tree` function above to reduce tree\ncounterexamples. With the newer `QCheck2` module, this is not needed\nas shrinking is built into its generators.\n\nFor example, we can rewrite the above tree generator to `QCheck2` by just\nchanging the `QCheck` occurrences to `QCheck2`:\n\n[source,OCaml]\n----\ntype tree = Leaf of int | Node of tree * tree\n\nlet leaf x = Leaf x\nlet node x y = Node (x,y)\n\nlet tree_gen = QCheck2.Gen.(sized @@ fix\n  (fun self n -\u003e match n with\n    | 0 -\u003e map leaf nat\n    | n -\u003e\n      frequency\n        [1, map leaf nat;\n         2, map2 node (self (n/2)) (self (n/2))]\n    ));;\n\n(* generate a few trees with QCheck2, just to check what they look like: *)\nQCheck2.Gen.generate ~n:20 tree_gen;;\n----\n\n\n`QCheck2.Test.make` has a slightly different API than `QCheck.Test.make`,\nin that it accepts an optional `~print` argument and consumes generators\ndirectly built with `QCheck2.Gen`:\n\n[source,OCaml]\n----\nlet rec print_tree = function\n  | Leaf i -\u003e \"Leaf \" ^ (string_of_int i)\n  | Node (a,b) -\u003e \"Node (\" ^ (print_tree a) ^ \",\" ^ (print_tree b) ^ \")\";;\n\nlet rec mirror_tree (t:tree) : tree = match t with\n  | Leaf _ -\u003e t\n  | Node (a,b) -\u003e node (mirror_tree b) (mirror_tree a);;\n\nlet test_buggy =\n  QCheck2.Test.make ~name:\"buggy_mirror\" ~count:200 ~print:print_tree\n    tree_gen (fun t -\u003e t = mirror_tree t);;\n\nQCheck_base_runner.run_tests [test_buggy];;\n----\n\n\n=== Preconditions\n\nThe functions `QCheck.assume` and `QCheck.(==\u003e)` can be used for\ntests with preconditions.\nFor instance, `List.hd l :: List.tl l = l` only holds for non-empty lists.\nWithout the precondition, the property is false and will even raise\nan exception in some cases.\n\n[source,OCaml]\n----\nlet test_hd_tl =\n  QCheck.(Test.make\n    (list int) (fun l -\u003e\n      assume (l \u003c\u003e []);\n      l = List.hd l :: List.tl l));;\n\nQCheck_base_runner.run_tests [test_hd_tl];;\n----\n\nBy including a precondition QCheck will only run a property on input\nsatisfying `assume`'s condition, potentially generating extra test inputs.\n\n\n=== Long tests\n\nIt is often useful to have two version of a testsuite: a short one that runs\nreasonably fast (so that it is effectively run each time a project is built),\nand a long one that might be more exhaustive (but whose running time makes it\nimpossible to run at each build). To that end, each test has a 'long' version.\nIn the long version of a test, the number of tests to run is multiplied by\nthe `~long_factor` argument of `QCheck.Test.make`.\n\n\n=== Runners\n\nThe module `QCheck_base_runner` defines several functions to run tests.\nThe easiest one is probably `run_tests`, but if you write your tests in\na separate executable you can also use `run_tests_main` which parses\ncommand line arguments and exits with `0` in case of success,\nor an error number otherwise.\n\nThe module `QCheck_runner` from the `qcheck` opam package is similar, and\nincludes compatibility with `OUnit`.\n\n\n=== Integration within OUnit\n\nhttps://github.com/gildor478/ounit[OUnit] is a popular unit-testing framework\nfor OCaml.\nQCheck provides a sub-library `qcheck-ounit` with some helpers, in `QCheck_ounit`,\nto convert its random tests into OUnit tests that can be part of a wider\ntest-suite.\n\n[source,OCaml]\n----\nlet passing =\n  QCheck.Test.make ~count:1000\n    ~name:\"list_rev_is_involutive\"\n    QCheck.(list small_nat)\n    (fun l -\u003e List.rev (List.rev l) = l);;\n\nlet failing =\n  QCheck.Test.make ~count:10\n    ~name:\"fail_sort_id\"\n    QCheck.(list small_nat)\n    (fun l -\u003e l = List.sort compare l);;\n\nlet _ =\n  let open OUnit in\n  run_test_tt_main\n    (\"tests\" \u003e:::\n       List.map QCheck_ounit.to_ounit_test [passing; failing])\n----\n\n\n=== Integration within alcotest\n\nhttps://github.com/mirage/alcotest/[Alcotest] is a simple and colorful test framework for\nOCaml. QCheck now provides a sub-library `qcheck-alcotest` to\neasily integrate into an alcotest test suite:\n\n[source,OCaml]\n----\n\nlet passing =\n  QCheck.Test.make ~count:1000\n    ~name:\"list_rev_is_involutive\"\n    QCheck.(list small_int)\n    (fun l -\u003e List.rev (List.rev l) = l);;\n\nlet failing =\n  QCheck.Test.make ~count:10\n    ~name:\"fail_sort_id\"\n    QCheck.(list small_int)\n    (fun l -\u003e l = List.sort compare l);;\n\nlet () =\n  let suite =\n    List.map QCheck_alcotest.to_alcotest\n      [ passing; failing]\n  in\n  Alcotest.run \"my test\" [\n    \"suite\", suite\n  ]\n----\n\n\n=== Integration within Rely\n\nhttps://reason-native.com/docs/rely/[Rely] is a Jest-inspire native reason\ntesting framework. @reason-native/qcheck-rely is available via NPM and provides\nmatchers for the easy use of qCheck within Rely.\n\n[source, Reason]\n----\nopen TestFramework;\nopen QCheckRely;\n\nlet {describe} = extendDescribe(QCheckRely.Matchers.matchers);\n\ndescribe(\"qcheck-rely\", ({test}) =\u003e {\n  test(\"passing test\", ({expect}) =\u003e {\n    let passing =\n      QCheck.Test.make(\n        ~count=1000,\n        ~name=\"list_rev_is_involutive\",\n        QCheck.(list(small_int)),\n        l =\u003e\n        List.rev(List.rev(l)) == l\n      );\n    expect.ext.qCheckTest(passing);\n    ();\n  });\n  test(\"failing test\", ({expect}) =\u003e {\n    let failing =\n      QCheck.Test.make(\n        ~count=10, ~name=\"fail_sort_id\", QCheck.(list(small_int)), l =\u003e\n        l == List.sort(compare, l)\n      );\n\n    expect.ext.qCheckTest(failing);\n    ();\n  });\n});\n\n----\n\n\n=== Deriving generators\n\nThe `ppx_deriving_qcheck` opam package provides a ppx_deriver to derive QCheck\ngenerators from a type declaration:\n\n[source,OCaml]\n----\ntype tree = Leaf of int | Node of tree * tree\n[@@deriving qcheck]\n----\n\nSee the according https://github.com/c-cube/qcheck/tree/master/src/ppx_deriving_qcheck/[README]\nfor more information and examples.\n\n\n=== Usage from dune\n\nWe can use the buggy test from above using the `qcheck-core` opam package:\n\n[source,OCaml]\n----\n(* test.ml *)\nlet test =\n  QCheck.Test.make ~count:1000 ~name:\"my_buggy_test\"\n   QCheck.(list small_nat)\n   (fun l -\u003e List.rev l = l)\n\nlet _ = QCheck_base_runner.run_tests_main [test]\n----\n\nwith the following `dune` file (note the `qcheck-core.runner` sub-package):\n\n[source,lisp]\n----\n(test\n (name test)\n (modules test)\n (libraries qcheck-core qcheck-core.runner)\n)\n----\n\nand run it with `dune exec ./test.exe` or `dune runtest`.\n\nWe recommend using the `qcheck-core` package as it has a minimal set of\ndependencies and also avoids problems with using\n`(implicit_transitive_deps false)` in dune.\n\nTo instead use the `qcheck` opam package and its included `QCheck_runner`:\n\n[source,OCaml]\n----\n(* test.ml *)\nlet test =\n  QCheck.Test.make ~count:1000 ~name:\"my_buggy_test\"\n   QCheck.(list small_nat)\n   (fun l -\u003e List.rev l = l)\n\nlet _ = QCheck_runner.run_tests_main [test]\n----\n\nwith the following `dune` file:\n\n[source,lisp]\n----\n(test\n (name test)\n (modules test)\n (libraries qcheck)\n)\n----\n","funding_links":[],"categories":["Libraries","Testing","OCaml"],"sub_categories":["Testing"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-cube%2Fqcheck","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc-cube%2Fqcheck","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc-cube%2Fqcheck/lists"}