I’ve been diving into OCaml recently and I’m using my usual diving board. Whenever I want to learn another programming language, the first thing that I write is an xUnit-style testing framework.
I don’t do it just because I love testing frameworks, although that’s reason enough, I guess – writing one makes learning and working in a new language much more enjoyable. No, I do it because implementing xUnit forces you into the nooks and crannies of a language early. The first thing that you do is look for reflection, exceptions, and object structuring. Once you’ve used those parts in a language, you may not know the best way to do things yet, but you can survive.
So, here's the core of my first-attempt OCaml xUnit – the test_case
class:
class virtual test_case test_name =
object(self)
method set_up = ()
method tear_down = ()
method virtual run_test : test_result -> unit
method run result =
result#test_started;
self#set_up;
try self#run_test result with
TestFailure message ->
result#add_failure test_name message;
self#tear_down
end;;
The run
method contains a straightforward implementation of the template method design pattern. In OCaml, virtual
means abstract. The run_test
method is implemented in subclasses of test_case
. Any exceptions that are thrown in a test are caught and passed to an instance of a class named test_result
.
As it stands, this little xUnit works. It's functional, but it isn't really functional. In OCaml, you'd expect to see some functional idioms in use, but it's hard to see where they should be used. In xUnit tests have state. They call set_up
to create it, and tear_down
to get rid of it. How do you handle state in a functional way?
The typical answer in functional programming is to parameterize. Imagine that we don't have classes and we want to do everything that test_case run
does. We'd end up with a function that looks something like this:
let run_test setup test_function tear_down result =
set_up;
try test_function result with
TestFailure message ->
add_failure result message;
tear_down
Nice, but look at all of those parameters! Makes you wish for objects again, doesn't it?
Well, not so fast. One of the cooler things you can do in functional languages is partial application. You can create a function that is defined as the application of another function to some subset of its arguments. So, if I want to have a set of tests that use the same setup and teardown, I can define a new function like this:
let xpath_test = run_test xpath_setup xpath_teardown
If you remember from above, run_test
takes four arguments: set_up
, tear_down
, test_function
, and result
. What we've done now is bind xpath_test
so that it uses xpath_setup
and xpath_teardown
any time that it is called. However, you still have to pass the other two arguments (test_function
and result
):
xpath_test a_test_function a_result
So, really, you don't need objects for template method-like work in a functional programming language. But, there is one little problem: state. When you're working in an object, you have shared state among your functions by default. When you working with functions alone, you have to pass state.
Here's how Ounit (a much more functional xUnit ported from Haskell) solves the problem:
let bracket set_up f tear_down () =
let fixture = set_up () in
try
f fixture;
tear_down fixture
with e ->
tear_down fixture;
raise e
The bracket
function assumes that set_up
will return a fixture of some sort. It also assumes that the test function and tear_down
will accept the fixture. It looks a little convoluted, but I suspect that template method looks convoluted to many people also.
I suspect that over time I'll start to see functional solutions from the very beginning, but it's nice to see that the path toward more functional style isn't very rocky.
Educational post. I've also been learning OCaml lately, but I'm purposefully staying away from it's OO features (and other non-functional features like references) initially so as to force myself to think functionally. Some people do this by learning Haskell because it's purely functional, but I figure I can get the same effect by learning the purely functional aspect of OCaml first and then after my functional-mindset has been built I can move onto those non-functional aspects of the language.... (and I'm trying not to cheat ;-)
Posted by: Phil | October 10, 2007 at 12:05 PM
Another way to minimize parameter passing in a functional way is to use higher order functions and closures. Maybe that is what the bracket function is leveraging, but I would need more context to say.
"An object is a poor man's closure."
"A closure is a poor man's object."
- some lisper
Posted by: Wilkes Joiner | October 19, 2007 at 05:58 AM
First of all, I think that you are doing yourself a disservice by using objects in OCaml. Given your OO background, I can understand why you do it. If you wish to learn functional and higher-order programming in OCaml, I would recommend to stay away from objects. (Sidenote: I prefer to distinguish between functional, meaning no side-effects, and higher-order, meaning with first-class functions.) You might even want to start with a simpler language, like Standard ML, that doesn't distract you with OO.
I've designed a few test frameworks in higher-order languages. Here is a simple way to think about tests. SNIPPED. I wanted to write example code, but the blog software doesn't recognize indented text as preformatted and after googling for a few minutes I didn't find instructions on how to include preformatted text.
Posted by: Vesa Karvonen | January 14, 2008 at 01:21 AM