vincent-hugot / qtest

Inline (Unit) Tests for OCaml
GNU General Public License v3.0
67 stars 8 forks source link

Best way to test functors? #35

Closed ergl closed 8 years ago

ergl commented 8 years ago

Hi, I've been using this library for a few days and it seems really useful, but I'm wondering what is the best way to test a functor. The docs mention using injections to go around this.

Currently I'm doing this:

(*$inject
  module StrGSet = Make(struct type t = string let compare = compare end)
*)

module Make (O : OrderedType) = struct (*$< StrGSet *)
  (* more code *)

  (*$QR foo
    Q.unit (fun () ->
      (* test code here *)
    )
  *)
  let foo args = (* ... *)

  (* more code *)
end (*$>*)

But I'm wondering if maybe there is a better way to go around this?

Now the test are coupled with whatever module you pass to the functor. My code only tests a particular implementation of Make with strings, but maybe the tests won't hold for int, or for any other type.

c-cube commented 8 years ago

I don't have a really satisfying answer to this, but can offer a few suggestions:

(*$inject
  module StrSet = Make(String)
  module IntSet = Make(struct type t = int let compare=compare end)
*)

module Make(X : Set.OrderedType) = struct
  module S = Set.Make(X)

  let foo l =
    let s = List.fold_right S.add l S.empty in
    S.cardinal s = List.length (S.elements s)
  (*$Q
    Q.(list printable_string) StrSet.foo
    Q.(list int) IntSet.foo
  *)
end
ergl commented 8 years ago

Yes, it seems like defining several modules and testing those will be the least difficult option — the test doesn't have a header, but I can live with that. Being able use the foo, bar as x idiom with different generators would be ideal though.

Regarding the weird Q.unit (fun () -> ..)., I'm trying to test several properties about my functions, but I haven't got around to implement my own custom generator as it looks quite daunting. For now I'm using the module's make : unit -> t constructor function to generate my values. For example:

(*$QR merge
    Q.unit (fun () ->
      let a = make () and
          b = make () and
          c = make ()
      in
      (merge a a) = a &&
      (merge a b) = (merge b a) &&
      (merge a (merge b c)) = (merge (merge a b) c))
  *)
c-cube commented 8 years ago

for writing a custom generator, it might look like this:

let gen : foo QCheck.Gen.t = fun rand -> ...
(* generate random values here using random state `rand` *)

let arb : foo QCheck.arbitrary = QCheck.make gen
(* the random generator + optional printers, shrinker. etc. *)

...

(* to use it *)
(*$Q
  Q.(triple arb arb arb) (fun (a,b,c) -> ...)
*)

It is possible to add a printer, shrinker, etc. to the call to QCheck.make.

ergl commented 8 years ago

Thanks for the quick explanation! I'm reading through the old docs and examples and I'll experiment with it.

As for the original issue, I consider it solved, so I'll go ahead and close it.