When developers practising test-driven development1 in other languages first move to Clojure, one thing that becomes painfully apparent is the slow startup time of a Clojure process. While JVM is partly to blame for this, it is Clojure itself that takes a significant amount of time to boot up.2
What follows is my take on what a productive TDD workflow could be in Clojure. This article expects the audience to be well versed in their respective editors and have launched/played with a Clojure REPL before. It also gets smoother to follow if one has already used Stuart Sierra’s reloaded3 workflow. While the article will make use Emacs as an editor, the workflow is what matters and should be fairly portable with a configurable editor.
Please take a note that your preferred editor should be able to talk to a running clojure process via nREPL as well be able to display the results from it if you want similar results.
Preparing the Clojure project
First things first, the Clojure project has to be prepared. The first step is to use an nREPL middleware for Clojure. Afterwards, we will make use of reloaded workflow.
Creating a new reloadable project
There is an
existing leiningen template
to generate new projects based on this workflow. If you are starting
from scratch, all you really need to execute is a single
lein new reloaded com.myname/awesome-project
Transforming into a reloadable project
This is probably the trickiest part, especially if you already have an existing Clojure project that you would want to restructure. If you are starting from scratch, you can skip this section.
This is entirely based on Stuart’s reloaded workflow. Since
this is major workhorse of the whole testing without restarting
approach, it would be recommended to read the
well detailed post
describing the workflow before continuing. Behind the scenes, the
workflow makes use of
The main idea of this workflow is to create a namespace which provides a system constructor. This constructor function should be able to return a new instance of the whole application/system5.
Some more functions are present in the namespace which manage the
starting and stopping of the application, convienently called
stop. Finally, the workflow makes use of the fact that Clojure
will automatically load file named
user.clj when starting a REPL in
the given environment (because Clojure REPL starts by default in the
user namespace, but this is configurable.)
user.clj file is added to the
dev folder, which
provides some more functions that initialize, start and stop system.
(reset) functions are provided that wrap
around all the other ones. They respectively start and reset(reload
all the changed/new files etc.) the process properly to have a clean
REPL to work on.
Since, this depends on the complexity and design of each individual project, it is recommended to follow the above mentioned post to properly integrate in one’s already existing application.
Preparing for nREPL
To enable/enhance the Clojure project to allow clients to talk it via
nREPL we will add a plugin called
cider-nrepl6 to the
CIDER is an Emacs package and
cider-nrepl as a clojure project
plugin enhances it’s capabilities, but the plugin is well consumed by
other editors too. (Eg.
vim-fireplace7 makes use of
Add the plugin to the
:plugins [... [cider/cider-nrepl "0.7.0"] ...]
Please visit the project page and make sure you’re using the latest version at the time of reading.
Trying it out
Now that is project has been set up, let’s make sure it’s working
normally. As expected, we would start a normal REPL, and test that the
(reset) are working properly.
After you first launch the REPL via leiningen,
run the functions provided by the reloaded workflow
user => (go) :ready user=> (reset) :reloading (.... a list of all your files in the project ...)
(reset) should reload all your files in the project and give
you a clean slate to work with in the running process. This is the
magic that the test workflow further ahead makes use of.
Tuning the editor
Talking over nREPL
As mentioned earlier, CIDER is the package used by Emacs to talk to the clojure project via nREPL. Install the package into Emacs the way you prefer it.
Please make sure that the
cider-nrepl plugin for the clojure
project and the
cider package for Emacs are compatible with each
other. This can be checked in the respective projects pages. As of the
writing, the release version numbers are synchronised between the
Executing tests on the nREPL
After installing the package/plugin, ensure that it’s loaded and open the clojure project that you want to work on. Load up the test file and fire off a REPL via editor.
After connecting to the REPL one can execute tests directly on it,
change the files in the editor, reload them in the REPL using
(reset) and rerun the tests.
This can be done in Emacs as follows.
;; Fire off cider to connect to a REPL. ;; This will take more than just a few seconds. M-x cider-jack-in<RET> ;; Also C-c M-j as provided by cider-nrepl
Once the REPL is ready, initialize the system and interact with it.
user => (go) :ready ;; Now one can use test runner functions as provided by testing libraries ;; for example clojure.test tests can be run as follows user => (clojure.test/run-all-tests) ;; after this one can change the files that they are working on and reset the REPL user=> (reset) :reloading (.... a list of all your files in the project ...) ;; now run the tests again / etc.
However, the above mentioned method is a poor wo/man’s way of running tests in the REPL. One can directly make use of the CIDER functionality to run/re-run all/selective tests.
This can be done in Emacs as follows.
;; Make sure the REPL is running and the project has started (go) ;; Open the test file you want to work on and execute the command M-x cider-test-run-tests<RET> ;; also C-c , ;; the above will run all the tests in the file and show results ;; either in the buffer (when failed) or in the echo line (if passed) ;; one can also selectively run tests ;; place the cursor on the test you want to run and execute M-x cider-test-run-test<RET> ;; also C-c M-,
While executing the tests has gotten a bit faster, the problem still remains that the REPL has to be reloaded everytime something is changed in the code. The final section ahead will deal with this.
Lightning quick re-runs
Let’s write some elisp to help us have the whole reload-and-run-test flow just a keypress away. Add the following to your relevant emacs script files.
This example only provides a keypress to reload all the tests in the namespace but you should be able to get the idea and extend it.
(defun cider-repl-command (cmd) "Execute commands on the cider repl" (cider-switch-to-repl-buffer) (goto-char (point-max)) (insert cmd) (cider-repl-return) (cider-switch-to-last-clojure-buffer)) (defun cider-repl-reset () "Assumes reloaded + tools.namespace is used to reload everything" (interactive) (save-some-buffers) (cider-repl-command "(user/reset)")) (defun cider-reset-test-run-tests () (interactive) (cider-repl-reset) (cider-test-run-tests)) (define-key cider-mode-map (kbd "C-c r") 'cider-repl-reset) (define-key cider-mode-map (kbd "C-c .") 'cider-reset-test-run-tests)
Now, you should be able to keypress
C-c . to reset and run all tests
via cider as well just be able to reset the REPL separately via
I wrote this guide, because I couldn’t find just one source of information for getting to a similar workflow presented above. I have pulled in ideas from a lot of other sources to be able to come up with this.
I hope this comes of some use to you as well. Feel free to pour in your suggestions and thoughts regarding improvement/corrections below.
Test-driven development here meaning, in the wide sense all styles of instant feedback testing when it comes to dynamic languages without a preference for test-first, test-driven, etc. The point, is being able to execute a test one is writing, without switching the ongoing context too much(eg. without leaving the editor). ↩
Called system, because it represents the whole system or the application that one is working on. ↩