Speed up TDD in Clojure with lein-reload

Personally, I am a big fan of the Test-driven Development (TDD) approach; iterative development by writing tests first and then implementing the required functionality is just so much fun and very efficient as well. As a very nice side effect you always maintain a working “product” and get your regression tests for free.

In this post I’ll write about how to significantly speed up TDD in Clojure with Leiningen and the lein-reload plug-in. There is a very well written introduction to Clojure and TDD written by Dave Ray. Note: while the guide by Dave Ray uses the Lazytest library written by Stuart Sierra for testing, I prefer to use the testing functionality as provided by clojure.test (which, by the way, is also written by Stuart Sierra plus et al.) as it is built into Clojure itself. Nonetheless, the guide by Dave Ray very nicely shows the ideas behind and approaches on TDD using Clojure.

Using Leiningen you can run all your tests via executing:

lein test

in the project directory.

While this technically works very well and there is actually nothing wrong with it there is the problem that this takes quite some time to run tests this way. Things wasting time are, e.g., the JVM being started or Leiningen firing up.

Within Leiningen there is the option to start an interactive shell in which you can execute Leiningen tasks:

lein interactive

In this shell you can for example simply type “test” to execute all unit tests the same way as if you would have typed “lein test” directly. Running tests repetitively in the interactive shell, you will notice that the first test run takes quite some time to execute while subsequent test runs execute much faster.

However, this alone is not suited for interactive TDD yet. The problem is that changes to your source files will not be picked up. To circumvent this issue there is the lein-reload plug-in by Sebastián B. Galkin: it keeps track of the modification dates of your source files and reloads those namespaces for which the source files have “changed” (actually, the modification date of the file changed).

The guide on the lein-reload homepage explains how to add this plug-in to a single project. Alternatively you can install it “globally” for a user by typing:

lein plugin install lein-reload "1.0.0"

To verify that the lein-reload plug-in works just go to one of your existing projects, enter the Leiningen interactive shell by executing “lein interactive”, type “reload”, change one of your source files (simply using touch from another shell: “touch src/foo/core.clj”, will work as well), and type “reload” in the interactive Leiningen shell again. You should now see a message that the namespace “foo.core” got reloaded.

The guide on the lein-reload homepage further shows how to add a hook to your project such that “reload” is always called when, e.g., “test” is run. However, this needs to be done for each project.

To add this hook globally for all projects I (for now) use a “hack-ish” solution. I simply added the following code to my “~/.lein/init.clj”:

(use 'leiningen.hooks.reload-on-test)

With this in place all my projects automatically make use of the “redefined” test task that first reloads all changed namespaces prior to running the tests. Note that I am currently using Leiningen 1.7.0.

From what I understand from the Leiningen website the above hack should not be necessary when using Leiningen 2.0.0 or above as there you can add to the built-in profiles via the “~/.lein/profiles.clj” file. Please correct me if I am mistaken.

Advertisement
This entry was posted in Snippets and tagged , , . Bookmark the permalink.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.