So far, we have implemented unit-test functions for OCaml functions. But now we define OCaml functions in OCaml modules. The goal of this lecture note is to evolve our unit-test paraphernalia and implement unit-test functors for OCaml modules.
In the lecture note about Environments, revisited, we defined environments as an abstract data type with the following signature:
type name = string;;
module type ENVIRONMENT =
sig
type 'a environment
val empty : 'a environment
val extend : name -> 'a -> 'a environment -> 'a environment
val lookup : name -> 'a environment -> 'a option
end;;
Still in the lecture note about Environments, revisited, we declared a unit-test function to illustrate the properties expected from a correct implementation of environments:
let test_environment_int empty extend lookup =
let b0 = (lookup "x" empty
= None)
and b1 = (lookup "x" (extend "x" 2 empty)
= Some 2)
and b2 = (lookup "x" (extend "x" 2 (extend "x" 3 empty))
= Some 2)
and b3 = (lookup "x" (extend "y" 2 (extend "x" 3 empty))
= Some 3)
and b4 = (lookup "z" (extend "y" 2 (extend "x" 3 empty))
= None)
(* etc. *)
in b0 && b1 && b2 && b3 && b4;;
After declaring two modules that allegedly implement environments, we then invoked the unit-test function:
module Environment_association_list : ENVIRONMENT =
struct
...
end;;
let () = assert (test_environment Environment_association_list.empty
Environment_association_list.extend
Environment_association_list.lookup);;
module Environment_function : ENVIRONMENT =
struct
...
end;;
let () = assert (test_environment Environment_function.empty
Environment_function.extend
Environment_function.lookup);;
A unit-test function that expects all the components of an environment requires us to first extract these components from the corresponding module.
A more concise alternative to unit-test functions is to declare a unit-test functor that expects this module:
module Test_environment_maker (Environment : ENVIRONMENT) =
struct
let () = assert (Environment.lookup "x"
Environment.empty
= None)
let () = assert (Environment.lookup "x"
(Environment.extend "x"
2
Environment.empty)
= Some 2)
let () = assert (Environment.lookup "x"
(Environment.extend "x"
2
(Environment.extend "x"
3
Environment.empty))
= Some 2)
let () = assert (Environment.lookup "x"
(Environment.extend "y"
2
(Environment.extend "x"
3
Environment.empty))
= Some 3)
let () = assert (Environment.lookup "z"
(Environment.extend "y"
2
(Environment.extend "x"
3
Environment.empty))
= None)
end;;
Then, instead of invoking the unit-test function, we invoke the unit-test functor:
module Vacuous = Test_environment_maker (Environment_alist);;
module Vacuous = Test_environment_maker (Environment_fun);;
A programming language is low levelwhen its programs require attention to the irrelevant.
While clear in its principle, the dot notation can be heavy in practice. Look at the definition of Test_environment_maker, for example, with all the occurrences of Environment..
OCaml provides language support to elide all the occurrences of Environment. in the definition of Test_environment_maker, using the open declaration in the body of the functor. In effect, the declaration:
open Environment
tells the OCaml parser that each identifier in the body of the functor that occurs in the signature of Environment (i.e., ENVIRONMENT) should be considered as coming from Environment. As for all the other identifiers, they are lexically scoped as usual.
Using this open declaration, the unit-test functor is easier on the eye:
module Test_environment_maker_shorter (Environment : ENVIRONMENT) =
struct
open Environment
let () = assert (lookup "x"
empty
= None)
let () = assert (lookup "x"
(extend "x"
2
empty)
= Some 2)
let () = assert (lookup "x"
(extend "x"
2
(extend "x"
3
empty))
= Some 2)
let () = assert (lookup "x"
(extend "y"
2
(extend "x"
3
empty))
= Some 3)
let () = assert (lookup "z"
(extend "y"
2
(extend "x"
3
empty))
= None)
end;;
In the same spirit as above,
See the resource file for the present lecture note.
Created [02 Apr 2019]