Should examples/tests/specs/whatever be DRY (Don’t Repeat Yourself)? I’ve been thinking (and talking and arguing) about the value of test names recently and whether they are just unnecessary duplication, but that’s the subject of a future discussion. This is about the actual content of your examples. So, should your examples be DRY?
In a word, no. DRY zealotry is a classic example of an over-adherence to Best Practices, which as James Bach argues are a bogus concept anyway. When you are writing software, including executable examples, your focus should be on clarity of intent. If the code could be any more obvious, you’re probably not done yet. And given that code is created once but read and changed many more times, it is healthy to develop a strong sense of responsibility to the guys coming along after you.
In tests, flow trumps DRY
The DRY principle says that the definition of any concept should appear once and only once in your code. This is an admirable aim in that if you have to change the behaviour of a Flooble, you want to be able to change it in one place and be reasonably confident that your change will apply consistently across the codebase. If there are multiple definitions of a Flooble, the chances are that not only will you not catch them all, but that someone before you didn’t catch them all and the multiple definitions are already inconsistent, and who wants that?
However, when you are using examples to drive your code - a process I call Coding By Example but which most other people insist on calling Test-Driven Development - there is another principle in play that I believe trumps the DRY principle. The examples tell a story about what the code does. They are the documentation narrative that will guide future programmers (including yourself when you come back to change this code in three months time and you’ve forgotten what it does). In this case, clarity of intent is found in the quality of the narrative, not necessarily in minimising duplication.
Some years ago I had my first experience of pair programming with Martin Fowler. That is, I had done quite a bit of pair programming, just not with Martin before. We were looking at some ruby code I had written test-first, and Martin asked to see the tests, “to find out what the code does”. Then he did a rather odd thing. He started moving the tests around. I had a few helper classes and utility methods in the source file, neatly at the end out of the way. He moved them up and dropped them inline just ahead of the first test that used them.
Madness! I thought - now the supporting code is all over the place! It really offended my sense of tidiness. But then I saw a pattern beginning to emerge. The test code was starting to read like a story. He would introduce these little methods and classes just before their one walk-on line in the narrative. It was quite an eye-opener for me. The test code flowed, and unfolded the story of the class under test. (I know I’m using TDD vocabulary - this was in my pre-BDD days.)
Go with the flow
The A-ha! moment for me was when I imagined reading a story book where the plot and characters had been DRYed out. Everything would be in footnotes or appendices. All the character descriptions, plot elements, subtexts etc. would be carefully extracted into fully cross-referenced paragraphs. Great if you are “reading” an encyclopaedia, not so appropriate if you want to get into the flow and find out what happens. You would be forever flicking back and forth in the book and you would very quickly forget where you even were in the story. In the words of the old joke, a dictionary has a lousy plot but at least they explain all the words as they go.
So here’s my challenge to you. When you read through a test case, or spec file, does the story unfold? Does it start by introducing the object’s most important responsibilty? Does it then introduce the edge cases in descending order of priority? If the test uses helper classes and methods are they tucked away at the end, or worse yet in an entirely different file that I am expected to “just know” is there?
Try to avoid using
before blocks or
setUp methods - especially in an abstract test class. Just call the method that does the setting up directly from the example. Don’t leave me to guess there might a magic method in a different class that is being invoked before the test even runs - that’s just not fair.