Dan North just blogged about how the DRY may not apply to stories/tests/specs:
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 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.
That’s a very interesting point. In a project some time ago I could see the pain that DRY test cases can cause. The team was great, we had enough tests and bugs were rare. Code quality was all right since the safety net provided by TDD allowed everyone to refactor mercilessly.
The problem for the new developers was to actually understand the system. As most agile projects the specifications were expressed in automated tests; but in this case those were written with DRY instead of readability in mind.
Looking into a well written and Domain-Driven Designed code you can understand the domain and how business concepts relate to each other but it is still very hard to understand why those objects act like that. The "why" question is not answered by the production code, it is answered by tests and they should be readable for that matter. I like to think of tests as executable story cards, for me is often more important for a test to be understandable than DRY.
But we all know that code duplication is terrible. How can we avoid that and still have readable tests? In a recent project we tried to follow the same approach as RSpec’s Story Runner, using lots of Behaviour-Driven Development concepts to create really expressive tests. We did DRY our tests a bit but instead of magical setUp()-and-tearDown() methods that contained the common code we preferred to extract those in methods with names that were meaningful in that domain, both to the developer as to the user.
As a quick simple example, suppose you have a test like this:
public class MessagesShouldBeProcessedTest {
MessageHandler handler;
Publisher fakePublisher;
Message msg1 = new Message("m1", true);
Message msg2 = new Message("m2", true);
Message msg3 = new Message("m3", true);
Message invalidMsg = new Message("invalid", false);
@Before
public void setupMocks() {
handler = mock(Handler.class);
}
@Before
public void setupPublisher() {
fakePublisher = new FakePublisher();
}
@After
public void checkMocks(){
verifyAll();
}
@Test
public void allMessagesShouldBeProcessed() {
setupWith(msg1, msg2, msg3);
handler.handle(msg1);
handler.handle(msg2);
handler.handle(msg3);
replayAll();
process();
}
@Test
public void shouldNotHandleInvalidMessage() {
setupWith(msg1, invalidMsg, msg3);
handler.handle(msg1);
handler.handle(msg3);
replayAll();
process();
}
private void process() {
fakePublisher.publishAll();
}
private void setupWith(Message... msgs) {
for (Message message : msgs) {
fakePublisher.prepareToPublish(message);
}
}
}
This code uses instance variables and before/after methods to minimise repetition but the readability was poor. Instead of writing code like that we would write this:
public class MessagesShouldBeProcessedTest {
MessageHandler handler;
Publisher fakePublisher;
Message msg1 = new Message("m1", true);
Message msg2 = new Message("m2", true);
Message msg3 = new Message("m3", true);
Message anInvalidMsg = new Message("invalid", false);
@After
public void checkMocks() {
verifyAll();
}
@Test
public void allMessagesShouldBeProcessed() {
// given
aWorkingHandler();
aWorkingPublisher();
thatTheUserHasPosted(msg1);
thatTheUserHasPosted(msg2);
thatTheUserHasPosted(msg3);
// expect
handlerToBeCalledFor(msg1);
handlerToBeCalledFor(msg2);
handlerToBeCalledFor(msg3);
replayAll();
// when
messagesArePublished();
}
@Test
public void shouldNotHandleInvalidMessage() {
// given
aWorkingHandler();
aWorkingPublisher();
thatTheUserHasPosted(msg1);
thatTheUserHasPosted(anInvalidMsg);
thatTheUserHasPosted(msg3);
// expect
handlerToBeCalledFor(msg1);
handlerToBeCalledFor(msg3);
replayAll();
// when
messagesArePublished();
}
private void aWorkingPublisher() {
fakePublisher = new FakePublisher();
}
private void aWorkingHandler() {
handler = mock(Handler.class);
}
private void thatTheUserHasPosted(Message message) {
fakePublisher.prepareToPublish(message);
}
private void handlerToBeCalledFor(Message message) {
handler.handle(message);
}
private void messagesArePublished() {
fakePublisher.publishAll();
}
}
The amount of code is pretty much the same but I think the latter is far more readable than the first one, even for a extremely simple example. We still had some noise like the unintuitive flow caused by Easymock’s replay and verify methods but it was a lot better already. Most of the utility methods used in the given/then/when blocks are shared among tests so most were extracted into a common super-class. When a new developer joined the team it wasn’t hard for him to understand the test, although it took a while to learn the “new” way of writing tests.
And what about JBehave? Well, I really don’t like JBehave 1.x, I’m waiting for JBehave 2 official release. The new version has these ideas more easily expressed, like in the example:
public class GridSteps extends Steps {
private Game game;
private StringRenderer renderer;
@Given("a $width by $height game")
public void theGameIsRunning(int width, int height) {
game = new Game(width, height);
renderer = new StringRenderer();
game.setObserver(renderer);
}
@When("I toggle the cell at ($column, $row)")
public void iToggleTheCellAt(int column, int row) {
game.toggleCellAt(column, row);
}
@Then("the grid should look like $grid")
public void theGridShouldLookLike(String grid) {
ensureThat(renderer.asString(), equalTo(grid));
}
}

Phillip, have you looked at Concordion? (http://www.concordion.org) I’d be interested to hear what you think of it.
Hey Phillip,
Liked the second test. Despite the fact that you’re duplicating code, I think the main purpose of a test is to be readable, so you should know the context before verifying any behaviour (setups), so it’s easier to spot the outcome of this verification. Tear downs should always be hidden when possible.
@David Peterson, regarding Concordion, we’ve being using it in my current project. It’s pretty flexible in the way that you can write your specification, but poor in terms of writing assertions. Unless your client wants the HTML output to put in a Wiki, I wouldn’t recommended using it. You can achieve pretty much the same result by writing tests in a Domain-Driven Design way (feedback from a had after a business sign-off).
Cheers,
Alexandre.
But you could change you mock framework and get rid of that replayAll(); eh
See you!
Phillip,
Some bdd stuff is coming from .net community.
I wrote the folowing code after some research on NBehave.
http://rafanoronha.net/testando-com-bdd/
Please don’t note the lambdas stuff… ;D
[]’s