Sunday, September 2, 2012

Before and after logic in the clojure.test framework

In Clojure's clojure.test framework, there are 3 facilities for setup and teardown, or "before" and "after" logic, to use RSpec terms. Overall, clojure.test is more similar to the xUnit style of tests, but the testing macro provides a bit of an RSpec feel.


/*---[ oneTimeSetUp and oneTimeTearDown ]---*/

In JUnit there used to be "oneTimeSetup" and "oneTimeTearDown" methods you could create. In JUnit 4 you now use the @BeforeClass and @AfterClass to mark methods to be used this way.

In clojure.test you define a :once fixture - method that will be called before any tests are run and it will be passed a function to call that will invoke all the tests. From this method, you can invoke any one time initial setup and final tear down functionality you need. Then you register it with the framework using its use-fixtures method:


/*---[ setUp and tearDown ]---*/

The logic for a setup and teardown method that will be called before and after each test is similar: you define an :each fixture and register it as a callback. The each fixture is also passed a function to invoke:

Putting those together you have:

Here's I've added a line to printout the type of thing it is passed, just so we can see that it is indeed a function. Here's the output from my run with three tests defined (not shown):

=> (clojure.test/run-tests)

Testing crypto-vault.core-test
one time setup
clojure.test$test_all_vars$fn__7134
setup
clojure.test$test_all_vars$fn__7134$fn__7141
teardown
setup
clojure.test$test_all_vars$fn__7134$fn__7141
teardown
setup
clojure.test$test_all_vars$fn__7134$fn__7141
teardown
one time teardown

Ran 3 tests containing 10 assertions.
0 failures, 0 errors.
{:type :summary, :pass 10, :test 3, :error 0, :fail 0}

You can see that our fixture methods are being passed references to "subfunctions" to the test-all-vars function. If you inspect the source code for that function, you see that you can also specify multiple :once and :each fixtures, if you need to.


/*---[ The 'testing' macro ]---*/

The each and once fixtures basically provide what JUnit and most xUnit frameworks give you.

BDD "spec" frameworks like RSpec and Jasmine, go a little further and allow you to define test contexts with their own before and after callbacks and you can nest contexts within contexts arbitrarily deep.

One value of that model is better documentation of each section of the tests. Another is finer control over before and after logic.

clojure.test does not provide nested before and after callbacks, but you can use the testing macro to define sub-contexts and then use let bindings to define any "setup" for that context.

If you really need a nested "teardown" within a testing subcontext, you can do it all in a try/finally structure:

testing contexts can be nested within each other if that makes sense for what you are doing.

Whether you put the teardown logic in a finally block or a teardown fixture is largely one of taste, but I prefer to keep the test focused on the test itself and leave the boilerplate to the fixtures.

1 comment: