Search code examples
listtestingsmltypechecking

Create test framework for testing functions with different types


I'm trying to create a testing framework for my homework assignment. The idea is to pass a list of tests (each of them consists of a function, parameters and and an expected value) to a function that will do the testing and then print a symbol that represents success, failure or an error (exception raised).

The problem is that functions as well as paramters may have different types like int * int -> int or string list -> bool. I store them in tuples but I can't put them in a list since they have different types.

One way to do this is to create a datatype that will have a constructor for each case but it's tedious. So my question is there a simple way to do this?


Solution

  • One trick is to package up all the functionality you need into a functions of type unit -> unit. Then you can put these into a list.

    Let's build up a small example. I might implement a small testing framework like so:

    datatype 'a result = Value of 'a | Raised of exn
    
    type ('a, 'b) test =
      { func : 'a -> 'b
      , input : 'a
      , check : 'b result -> bool (* checks if the output is correct *)
      }
    
    fun runTest {func, input, check} =
      let val result = Value (func input) handle e => Raised e
      in if check result then
           print "Correct\n"
         else
           case result of
             Value _ => print "Incorrect\n"
           | Raised e => print ("Raised " ^ exnMessage e ^ "\n")
      end
    

    Here's an example usage:

    val t1 =
      { func = op^
      , input = ("hello", "world")
      , check = (fn Value "helloworld" => true | _ => false)
      }
    val t2 =
      { func = Word.fromInt
      , input = 5
      , check = (fn Value 0w5 => true | _ => false)
      }
    val _ = runTest t1
    val _ = runTest t2
    

    But now, as you point out, you have the problem that you can't do this:

    val tests = [t1, t2] (* doesn't typecheck *)
    val _ = List.app runTest tests
    

    To fix this, I'd recommend doing the following.

    type test' = unit -> unit
    
    fun make (t : ('a, 'b) test) : test' =
      (fn () => runTest t)
    
    fun runTest' (t' : test') = t' ()
    

    This approach packages up all the functionality you need from a test (that is, running it, checking that it is correct, etc.) into a function of type unit -> unit. Now, all of your "tests" are the same type, and can be put in a list.

    val t1' = make t1
    val t2' = make t2
    
    val tests' = [t1', t2']
    val _ = List.app runTest' tests