TDD and Clojure

June 5, 2013 § 9 Comments

I got some comments from Renzo on a previous post I wrote about Clojure and testing, and some feedback on a recent talk I gave, via email and talk down the pub after. I ponder some of these points here.

I’ve made the point that using Clojure really does force a reappraisal of the TDD situation, especially if you’re coming from an OO background. I still think this is the case, but at the same time I acknowledge that’s there nothing uniquely special about Clojure that makes this so.

Renzo suggests that some people are fine without tests because ‘they can easily grasp a problem in their head and move on’. I agree with this but I also think that the language of choice does have a direct bearing. Put bluntly, I think that most people using Clojure will write less unit-tests compared to traditional OO programming.

It’s all about feedback.

Rapid Development

If you were to create a shopping list of things you really want for your development experience then what would you put at the top? I think most people would put ‘rapid development’ at first place. I.e. you want to make a change somewhere in your codebase, hit F5 in your browser or wherever, and then to see the changes appear immediately.

And to be honest I think this aspect of development – rapid feedback – is absolutely key. If you can just change code and see the results immediately then it really does lessen the value of writing actual test classes to gauge the results of intended changes. This may not be completely healthy, and one could imagine more cases of sloppy hacking as a result, but I’d be surprised if there wasn’t a coloration between fast feedback of changes and the amount of unit-test classes written. For example I’d be interested to know if Java devs using the Play web framework end up writing less unit-tests as a result of moving from Spring/Struts etc. I’d expect so.

Clojure is nothing special for giving rapid-feedback on changes, just glance across at the dynamic scripting languages such Ruby, Python and PhP. But if you’re coming from the enterprise Java world, where I’ve spent some years, then this is one of the first things that really knocks you on the head.

REPL

I think the next item on the list of ‘top development wants’ will often be an interactive command prompt; the REPL, for reasons that are well documented.

I gave a recent talk and one person said that it feels strange for him as a Python developer that I and others are talking up a tool that so many have taken for granted for decades, like we’re raving about this thing called the wheel.

This is a fair point, but it doesn’t take away the amazing difference for devs coming from the traditional statically typed OO landscape, where the REPL is not so much employed.

And I think this does have a direct affect on the TDD process. Primarily, you have a place to explore and to play with your code. Put the other way if you don’t have a REPL and you don’t have ultra-fast feedback on changes, then what do you do? You’d probably want to write lots of persistent unit-tests, or main methods, just so that you can pop into the codebase and run small parts of it.

FP and Immutability

Immutability makes for a saner code-base. I make the point that if you work with a language that’s opinionated in favour of propagating state-changes, then it would perfectly reasonable to want more tests in place, as to pin desired behavour down.

FP and dynamic languages lead to a lot less code. There’s less ceremony, less modeling. Because you’re managing less code you do less large scale refactorings. Stuff doesn’t change as much once it’s been written. The part of traditional TDD that leaves behind test-classes for regression purposes is less needed.

Regression Tests

It’s my current opinion that what you get left out of TDD once you have amazingly fast feedback and a REPL is regression testing.

This is still needed of course, but there’s huge value as treating it as a separate concern to other forms of testing. I’ve been guilty of seeing it all wrapped up in TDD, as not something distinct warranting its own attention.

In some respects deciding how much regression tests to apply becomes a bit like ‘how much performance tuning do we need’. It depends on the business context, and it’s probably better not to do it prematurely, and with consideration and measurement.

There is an argument that unit-tests serve a purpose for live documentation. I would just question this. Although some FP code can be difficult at first glance it can still be written expressively, using vars with names more than one character. And there’s always comments with examples.

Conclusion

This is my view on why devs using Clojure seem to write less unit-tests. I wouldn’t go to the next level of dogma and dare say that devs shouldn’t ever write unit-tests. In fact I’d expect any unit-tests on a Clojure project to be of high quality and to really have a purpose. I’m also a bit reluctant to judge TDD as a whole. I think TDD for masses of devs including myself has taught us how to effectively refactor, and through some definition of TDD we’re probably still doing it, whether it’s through the REPL or just by hitting F5.

§ 9 Responses to TDD and Clojure

  • Tom says:

    Not sure about the repl being the reason you don’t seem to write so many unit tests. After all, Kent Beck (who invented the xUnit approach to unit testing and was a founder of the TDD movement) came up with those approaches working in Smalltalk which has a repl equivalent and is incredibly dynamic.

    So, assuming your quality is still good ;-), there must be something else. Personally I think its referential transparency and immutability. Referentially transparent functions map a domain to a co-domain, and that’s it. Objects, even simple objects, set up state, and then respond to messages dependent on that state, its a lot more complex and hard to reason about.

    It may be, amazingly, that functional programming actually does live up to some of its hype about the ease of reasoning about it.

    (P.S. Good talk on Monday, re-energised my enthusiasm for Clojure)

  • Tom says:

    and another thing…

    One of my concerns when reading you posts about testing was edge cases. I have written code and played with it quite a bit to get happy and then (someone else) finds a failing an edge case. Unit tests help to formalise the developer testing to make sure it really does what it says.

    However, in transparent and immutable, functional code there are fewer edge cases so maybe trialling in the repl is enough.

    (At least in most cases…)

  • Manuel says:

    I’m thinking about this quite a lot lately and I’d like to take a moment to thank Jon for taking the time to write down his thoughts on this.

    I think that the major properties that has to be taken into account when talking about TDD are:

    * REPL: ultra-fast feedback loop
    * Functional approach: immutability, small and focused pure functions and maybe a bottom-up design process
    * Dynamic, strong typing: more flexibility, less complexity, but less help from the compiler (at compile time) too

    Clearly a language/approach like this is different from, say, Java. I feel that some of the reasons TDD is so useful in that context are:

    * Verbosity and lack of a REPL to prototype, explore and in general get rapid feedback.
    * Connected to the previous point: a design approach focused on a network of stateful objects that hide that state (and data) behind ad-hoc APIs.

    In this light maybe a formal TDD process is less useful with languages like Clojure (please note that I said *maybe* and *less*).

    You can experiment and design a system composed of mostly small pure functions very quickly, so that a formal TDD approach is less useful because you can get that fast feedback on your design anyway. But you can (and probably should) stop sometime and pin down some data-driven acceptance tests to catch *regressions* and ensure that some major area of functionality are there and behave correctly (and maybe some unit tests for some important functions). Maybe that acceptance-tests (and unit tests) can be written *before* diving into the nitty-gritty design of the system.

    Tom, you made some interesting comments. For example you talked about edge cases. I think that this is a fair observation, but consider that:

    * These edge cases can be tested at the REPL the same way you test for “happy cases”. Like with TDD in which you write tests for both happy and edge cases.
    * Testing for edge cases at the REPL requires discipline, but this is the case with TDD too. I’ve seen objects designed with TDD entirely without testing for any of such cases.

    Your observation about Smalltalk is also interesting, but I don’t have enough informations on this to make a meaningful comment.

    Disclaiment: I’m “test infected” and I use TDD extensively when I work in languages like Java, but I think it’s important to be pragmatic and consider this practice in the context you are in. Consider that I don’t have designed large systems in Clojure (yet). Maybe TDD is just as important in Clojure. I think that this is an interesting discussion and willing to listen to different opinions.

  • Jon Pither says:

    Hi Tom,

    I agree that FP as oppose to OO lessens the need for unit-tests, taking away the REPL and fast-feedback from the argument. Immutability is a change changer… functions are just massively simpler than objects.

    Good point – probably should have made more of it.

  • Jon Pither says:

    Hi Manuel,

    Good comments & thanks. WRT to one thing you said ‘But you can (and probably should) stop sometime and pin down some data-driven acceptance tests to catch *regressions*’,

    I’m pondering where the line is between regression and acceptance tests. I’m very concerned about ‘specification by example’ type tests, using frameworks like FIT, because I think they come with a large cost. I think if you’ve got a fast enough development process in place then prototyping and showing the user fast and frequent changes lessens the need for capturing the story requirements up front.

    Often I think regression is more important, making sure that required functionality is bedded down and doesn’t change. For this you don’t need the traditional sitting with QAs and the business, you just need to come up with a really good framework for the tests implementation.

    The lines are blurred though, no doubt. I’d say Acceptance Tests are something you write up front and liase with the business to produce, perhaps where the tests themselves are written in a format the business can understand.

    I think regression tests are there to serve the development team as a safety net, and need not be written up front.

    I’m not suggesting you and others don’t know this already, just trying to get my own head around all this.

  • Chris Ford says:

    I’ve found Clojure more fertile ground for unit testing than Java.

    True, the REPL means you can exercise your code without a wrapping unit test. However, I find the REPL complementary to unit testing as I pull things out of REPL sessions into tests, and run existing tests interactively through the REPL.

    Immutability and pure functions make writing tests easier, and makes the resulting tests clearer, than my experience of Java.

    On the other hand, the extra heights of abstraction that Clojure allows me to reach means I have more need of concrete examples to keep my code grounded.

  • Thanks for this thought-provoking post. I’m not very experienced with Clojure but more and more opinionated on TDD benefits in general.

    It feels to me that most of your post is about unit tests more than TDD. I really believe regression is the reason you want a lot of unit testing, no matter the language you’re using. In particular, a good unit-test coverage gives you confidence to refactor at will. In Clojure, you can also get immediate feedback on regression when using Midje autotest, which links back to your points on Rapid Development and REPL.

    About TDD itself, I think you end up with a better design when starting with the test, especially when doing top down development and refactoring as you go. Again, Midje offers great support for this methodology:
    https://github.com/marick/Midje/wiki/The-idea-behind-top-down-development

  • Jon Pither says:

    Hi Damien,

    Yes this post is more about unit-tests than TDD as a whole. But you can’t really discuss unit-tests in isolation.

    I think that one of the main problems of TDD – perhaps the biggest problem – is that it’s totally complected (yep – I sound like a Hickey fanboy, but it’s the word in mind that most fits). It mashes together so many concerns – design assistance, regression, fast-feedback.

    I would humbly disagree with you that TDD’d unit-tests in general serve for good regression, because regression is often not the reason that the unit-tests were written in the first place, moreover it’s likely the regression aspect is a side-effect of the TDD step-by-step design process. Whilst traditional TDD produced unit-tests may well give you good regression, it’s coming at a higher cost – having lots of test-code to manage.

    I think you’ll get regression more cheaply and better targeted if you think about it as it’s own concern.

    That said, you may well go full circle and decide that the need for regression is best served by having some Midje unit tests with the autotest feature turned on (we’ve got a few unit-tests kicking around).

    This sounds like a cop out, but it’s not – the starting points are different. Thinking about unit-tests for regression purposes mean you’ll probably write them only when you feel a direct need for them. Having them produced as a artefact of TDD means you’re likely have lots of them, and many of them probably won’t ever fail or be at all useful.

    :-)

  • [...] J. Pither: TDD and Clojure – “If you were to create a shopping list of things you really want for your development experience then what would you put at the top?” => 1. rapid feedback on changes, 2. REPL (place to explore and to play with your code <=> TDD), 3. FP and Immutability (“FP and dynamic languages lead to a lot less code. There’s less ceremony, less modeling. Because you’re managing less code you do less large scale refactorings.” => TDD needed less), 4. Regression Tests (“It’s my current opinion that what you get left out of TDD once you have amazingly fast feedback and a REPL is regression testing.”) [...]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

What’s this?

You are currently reading TDD and Clojure at Pithering About.

meta