ppx_pbt

PPX Rewriter for property based testing
README

Syntax extension for writing property based tests in OCaml code using properties
and generators abstraction.

New syntactic constructs

let <name> <args> = <expr>
[@@pbt {| <payload> |}]

Payload is defined as follow:

<payload> = <property> { ; <property> }

<property> = property_name <args> <gens>

<args> := { arg_identifier { , <args> } }
| _ 

<gens> = [ gen_identifier { , <gens> } ]
| _

Exhaustive list of property:

commutative                [gen, gen]
associative                [gen, gen, gen]
neutral_left    {neutral}  [gen]
neutral_right   {neutral}  [gen]
neutrals        {neutral}  [gen]
capped_left     {cap}      [gen]
capped_right    {cap}      [gen]
capped          {cap}      [gen]
eq_with_f       {f}        [gen, gen]
absorb_left     {absorb}   [gen]
absorb_right    {absorb}   [gen]
absorbs         {absorb}   [gen]
floored_left    {floor}    [gen]
floored_right   {floor}    [gen]
floored         {floor}    [gen]

Exhaustive list of gen:

int
uint

Usage

Using builtin properties and generators

let add x y = x + y
[@@pbt {| commutative[int, int] |}]

(* which becomes *)

let add x y = x + y

let test_add_is_commutative =
  Test.make ~name:add_is_commutative
  (QCheck.pair Pbt.Gens.int Pbt.Gens.int)
  (fun (x, y) -> Pbt.Properties.commutative add x y)

let _ =
  QCheck_run.run_tests ~verbose:true [ test_add_is_commutative ]

Use your own property

Local properties can be used instead of builtin properties:

let even f x = (f x) mod 2 = 0

let add x y = x + y
[@@pbt {| is_even[int] |}

(* which becomes *)

let even f x = (f x) mod 2 = 0

let inc x = x + 2

let test_inc_is_even =
  QCheck.Test.make ~name:inc_is_even
  Pbt.Gens.int
  (fun x -> even inc x)

let _ =
  QCheck_run.run_tests ~verbose:true [ test_inc_is_even ]

Properties comes either from:

  • the exhaustive list of property

  • your local definition

The local property must returns a boolean and takes as much paramaters that are
given in arguments and generators, it is translated into.

let test_<tested_function>_is_<your_property> =
  QCheck.Test.make ~name:<tested_function>_is_<your_property>
  <generators>
  (fun <pattern> ->
    <your_property>
    <tested_function>
    <arg0> ... <argN>
    <gen0> ... <genN>)

Use your own generator

Same mecanism is available for generators:

let abs_int = QCheck.map (fun x -> abs x) QCheck.int

let positive f x y = (f x y) > 0

let mul x y = x * y
[@@pbt {| positive[abs_int, abs_int] |}]

(* which becomes *)

let abs_int = QCheck.map (fun x -> abs x) QCheck.int

let positive f x y = (f x y) > 0

let mul x y = x * y

let test_mul_is_positive =
  QCheck.Test.make ~name:"mul_is_positive"
  (QCheck.pair abs_int abs_int)
  (fun (x, y) -> positive mul x y)
  
let _ =
  QCheck_run.run_tests ~verbose:true [ test_mul_is_positive ]

Use properties with arguments

Property based tests needs generators in order to check properties, but
additional arguments can also be our function parameters.

let zero = 0

let add x y = x + y
[@@pbt {| neutrals{zero}[int] |}]

(* which becomes *)

let test_add_is_neutrals =
  Test.make ~name:add_is_neutrals
  Pbt.Gens.int
  (fun (x, y) -> Pbt.Properties.neutrals add x)

let _ =
  QCheck_run.run_tests ~verbose:true [ test_add_is_neutrals ]

Property's arguments are the first parameters given to the property:

<property> <args> <gens> is translated as:

let ... =
  QCheck.Test.make
  ~name:"..."
  <gens>
  (fun <pattern>
    <call_to_property>
	<arg0> .. <argN>
	<gen0> .. <genN>)

Arguments are inlined with OCaml.identifer, therefore the argument must be
available at the scope of our generated test.

Install
Published
12 May 2021
Sources
ppx_pbt-v0.1.0.tar.gz
md5=627584e9f25d016e75ece04bde5bcfa0
sha512=18e3ab5fd0ac5b6b24d24f85dba38acb4f43254fc23da24a5ec58281e91bb6b92332c8601039dcbbb66dfcc36a20e06e84aa61c856cfd9d6f7cad21ab0177681
Dependencies
metaquot
>= "0.4"
qcheck
>= "0.17"
menhir
>= "20180523"
ocaml
>= "4.10.0" & < "4.14"
dune
>= "2.8.0"
Reverse Dependencies