Recently I promoted to do TDD, instead of “Tests First” development. Some people asked me what the difference is between them. In both cases we write tests first right?
So what is the difference?
I believe the difference is this:
Test First decribes your solution. TDD describes the problem
The difference could probably be explained best when using the coderetreat I had organized at the beginning of this year. Within this session I had experienced a great example to tell the difference between Tests first and TDD. To clarify the difference in this blog, we will be writing an implementation of Conway’s Game Of Life. It has the following rules:
- Any live cell with fewer than two live neighbours dies, as if caused by under-population.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overcrowding.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
And it looks like this:
Your assignment:
Write code that implements to the four rules above. It should be possible to apply these on an infinite grid
Test First describes your solution:
So the rules talk about “cells” and in your mind you’re already trying to solve this puzzle. In fact, I bet you’re already thinking about some array to put this matrix of cells into. Using a matrix we can easily determine neigbours and solve this puzzle…
We start with the first rule: “Any live cell with fewer than two live neighbours dies, …“.
We know it needs neighbours, so we need some boilerplate for that right?
The first test looks like this:
public class CellTest { @Test public void mustReturnTrueWhenAlive() { Cell cell = new Cell(1,0); Assert.assertTrue(cell.isAlive()); } }
Since we’re doing TDD (atleast we think it is, we’re actually doing Tests First…), we need to create this Cell class to make it compile.
public class Cell { private long x, y; public Cell(int x, int y) { this.x = x; this.y = y; } public boolean isAlive() { return true; } }
Before we can do anything with counting the number of neighbours, we need to determine what a neighbour is. Since adjecent cells are counted as neighbours, we start writing tests for this:
@Test public void mustReturnTrueWhenNextToAnotherCell() { Cell cell = new Cell(1,0); Cell adjecent = new Cell(1,1); Assert.assertTrue(cell.isAdjecent(adjecent)); } @Test public void mustReturnFalseWhenNotNextToAnotherCell() { Cell cell = new Cell(1,0); Cell adjecent = new Cell(3,3); Assert.assertFalse(cell.isAdjecent(adjecent)); }
And along with it the code:
public class Cell { private long x, y; public Cell(int x, int y) { this.x = x; this.y = y; } public boolean isAlive() { return true; } public boolean isAdjecent(Cell adjecent) { long diffX = Math.abs(adjecent.getX() - x); long diffY = Math.abs(adjecent.getY() - y); return diffX == 1 || diffY == 1; } public long getX() { return x; } public long getY() { return y; } }
Wait, stop, halt!
If the above sounds familiar, then I’ve got news: This is not TDD
Lets get back to the original question, what did we try to implement? Ah, it was the question:
“Any live cell with fewer than two live neighbours dies, as if caused by under-population”,
So where is the corresponding test for that?…
In fact, we have already three tests and a bunch of code, and we still are not able to answer that question.
What we’ve done so far is write tests first in order to prove a solution we already had in our minds. We did not let the tests guide us to a design. In fact, we already had a design in our heads and made the tests conform to those.
Lets do it in TDD, for real…
So how is it done ? – Test Driven Development
With a clean slate, we start over. And we start with the first rule:
“Any live cell with fewer than two live neighbours dies, as if caused by under-population”
So we create a test (we call it Test, because we do not think about Cells yet, in fact, we are only thinking about this very question).
@org.junit.Test public void anyLiveCellWithFewerThanTwoLiveNeighboursDies() { int neighbours = 1; Assert.assertTrue(neighbours < 2); }
So what does this do, it basically returns true when neighbours is lower than two. We do not call methods yet, we simply have our implementation within the test itself. In this phase, we already had Red (non compiling), Green (compiling and green test). On to refactor. How to get it more descriptive? We could do something like this:
@org.junit.Test public void anyLiveCellWithFewerThanTwoLiveNeighboursDies() { int neighbours = 1; boolean shouldDie = neighbours < 2; Assert.assertTrue(shouldDie); }
Do we need to do anything else? Certainly! But the essential rule is there already. It is one simple statement, no neighbour checking yet. And in fact, we will not need it to implement the four rules! Lets continue. I am serious, we will not modify the above code yet. We have yet to implement the other rules. Lets pick the second:
“Any live cell with two or three live neighbours lives on to the next generation.”
We have actually two cases here, for two and three live neighbours. Lets start with two:
@org.junit.Test public void anyLiveCellWithTwoNeighboursLivesOn() { int neighbours = 2; boolean shouldLiveOn = neighbours == 2; Assert.assertTrue(shouldLiveOn); }
Not that much different is it? How about we add the third test for the second rule:
@org.junit.Test public void anyLiveCellWithThreeNeighboursLivesOn() { int neighbours = 3; boolean shouldLiveOn = neighbours == 3; Assert.assertTrue(shouldLiveOn); }
The total test class looks like this now:
public class Test { @org.junit.Test public void anyLiveCellWithOneThanTwoLiveNeighboursDies() { int neighbours = 1; boolean shouldDie = neighbours < 2; Assert.assertTrue(shouldDie); } @org.junit.Test public void anyLiveCellWithTwoNeighboursLivesOn() { int neighbours = 2; boolean shouldLiveOn = neighbours == 2; Assert.assertTrue(shouldLiveOn); } @org.junit.Test public void anyLiveCellWithThreeNeighboursLivesOn() { int neighbours = 3; boolean shouldLiveOn = neighbours == 3; Assert.assertTrue(shouldLiveOn); } }
We have done some little TDD cycles already. We started describing the problem domain, and we added the minimum amount of code to make this work. We did not yet start write any production code yet. Now one of the most important steps in TDD should be taken: Refactor. (Remember it is Red – Green – Refactor!)
With the third test, we clearly see duplication. The shouldLiveOn can be extracted to a method. Lets do that:
import org.junit.Assert; public class Test { @org.junit.Test public void anyLiveCellWithOneThanTwoLiveNeighboursDies() { int neighbours = 1; boolean shouldDie = neighbours < 2; Assert.assertTrue(shouldDie); } @org.junit.Test public void anyLiveCellWithTwoNeighboursLivesOn() { int neighbours = 2; Assert.assertTrue(shouldLiveOn(neighbours)); } @org.junit.Test public void anyLiveCellWithThreeNeighboursLivesOn() { int neighbours = 3; Assert.assertTrue(shouldLiveOn(neighbours)); } private boolean shouldLiveOn(int neighbours) { return neighbours == 3 || neighbours == 2; } }
We could refactor out the neighbours var to a constant, which should give us even smaller tests.
At this point we have now our first method which could eventually be moved out of the test class into some other class (we have yet to think of a name for). As you can see, the design of our code is being driven by the tests. So this may like trivial and like ‘cheating’. In fact, as I see it we are actually answering the real questions. We tend to write code for stuff we cannot possibly be sure of that it is correct. Did you see any line say that the Game of Life in this situation should be on a 2D grid? What if it would be 3D? What if we did not know yet if it would be 2D or 3D?
This sounds a lot like real-life isn’t it? Where your customer does not always know exactly what he wants.
Another good thing is, we can implement all rules like this. Eventually we end up with a test class that contains several methods. From there on we can think of a logical way to group them. Methods grouped together will form classes. We tend to group methods logically. When we define the problem domain we know better what classes should exist. Again, our tests drive the design. Instead of the other way around.
Here is an impression how the four rules implemented might look like:
package com.fundynamic.coderetreat; import org.junit.*; public class Test { public static final int StarvationThreshold = 1; public static final int OverpopulationThreshold = 4; public static final int MinimumRevivalThreshold = 3; public static final int MaximumRevivalThreshold = 3; @org.junit.Test public void liveCellShouldDieIfLessNeighboursThanStarvationThreshold() { int amountNeighbours = StarvationThreshold; Assert.assertEquals(false, livesOnToNextGeneration(amountNeighbours)); } @org.junit.Test public void liveCellShouldDieIfNeighboursEqualToStarvationThreshold() { int amountNeighbours = StarvationThreshold; Assert.assertEquals(false, livesOnToNextGeneration(amountNeighbours)); } @org.junit.Test public void liveCellShouldLiveIfTwoNeighbours() { int amountNeighbours = StarvationThreshold +1; Assert.assertEquals(true, livesOnToNextGeneration(amountNeighbours)); } @org.junit.Test public void liveCellShouldLiveIfThreeNeighbours() { int amountNeighbours = 3; Assert.assertEquals(true, livesOnToNextGeneration(amountNeighbours)); } @org.junit.Test public void liveCellShouldDieIfFourNeighbours() { int amountNeighbours = 4; Assert.assertEquals(false, livesOnToNextGeneration(amountNeighbours)); } @org.junit.Test public void liveCellShouldDieIfEightNeighbours() { int amountNeighbours = 8; Assert.assertEquals(false, livesOnToNextGeneration(amountNeighbours)); } @org.junit.Test public void deadCellShouldReviveIfMinimumRevivalThreshold() { int amountNeighbours = MinimumRevivalThreshold; Assert.assertEquals(true, revivesInNextGeneration(amountNeighbours)); } @org.junit.Test public void deadCellShouldReviveIfMaximumRevivalThreshold() { int amountNeighbours = MaximumRevivalThreshold; Assert.assertEquals(true, revivesInNextGeneration(amountNeighbours)); } @org.junit.Test public void deadCellShouldNotReviveIfLessNeighboursThanMinimumRevivalThreshold() { int amountNeighbours = MinimumRevivalThreshold -1; Assert.assertEquals(false, revivesInNextGeneration(amountNeighbours)); } @org.junit.Test public void deadCellShouldNotReviveIfMoreNeighboursThanMaximumRevivalThreshold() { int amountNeighbours = MaximumRevivalThreshold +1; Assert.assertEquals(false, revivesInNextGeneration(amountNeighbours)); } private boolean livesOnToNextGeneration(int amountNeighbours) { return amountNeighbours > StarvationThreshold && amountNeighbours < OverpopulationThreshold; } private boolean revivesInNextGeneration(int amountNeighbours) { return amountNeighbours == MinimumRevivalThreshold; } }
But you did not even get to any cell? How is this any good?
It is true that cells play a role in the Game of Life eventually. But they do not play a role in answering the four questions. In fact, what are cells? We might be talking about squared cells or triangled or circled cells. Perhaps the requirement is to write a 3d version of a cell. Or you might want to use hexagons. If you put the rules logic into a cell, it gets very hard to modify your code because you have put too much responsibility in one class.
TDD prevents you from doing this. It prevents you from doing ‘design upfront’.
Also, if you started with using Cells and the matrix and all that. I would wonder how you would implement the last rule (reviving cells). How would you solve this problem?
Bottom line
Writing tests before your production code (test first) is not the same as TDD. TDD is about how your tests drive your design. Only then you can say if you are doing TDD or actually are just writing tests proving your own solution you have thought about before-hand.
It is hard to not think ahead of your design, and instead trust on our tests to let the design emerge itself. This requires practice. Practicing this can be done in coderetreats for instance.
Disclaimer about design…
TDD works great on a lot of levels in your architecture. This does not mean you can just do everything with TDD. Good architectural design requires thinking. You can’t just ‘do TDD’ and magically have a perfect design emerging. However, TDD will give you surprisingly elegant results. Especially, the more specific your problem (with a clear scope) the better TDD yields results.
Hi, Stefan,
Surely, you posted this blog a day early?
Look at the amount of time you’ve wasted?
Are you serious? Do you work in a development shop?
While you’re busy faffing around writing test case after ridiculous test case (and presenting ridiculous straw-men as alternatives), your entire time will have commited the first, working version to the trunk.
You’ve to close the TDD books that you’ve clearly spent a lot of money on, and start building production code.
Seriously, dude, TDD is a methogological smell and you’ve done nothing to further its cause by this post.
Though, thanks for that laugh.
If you had read my about page, you knew where I work and what I do. Clearly you are missing the point of this post, or other posts I’ve written.
Somehow you seem to believe that “production code” is code that matters. And test code does not. I believe they are complementary. Without tests you cannot be sure if changes in your prod code is still working.
You also seem to think TDD is some ridiculous theory. If so, I’d advice you to do some research of your own. Not only has it been researched by a wild number of organizations (including Google and Microsoft), it also became more like a de-facto standard on how to work as a Software Engineer.
I believe TDD is the way to go. And i believe it also produces code you only need (opposed to what you *think* you need). It proves your code works, provides a super fast feedback mechanism and gives you a very flexible design. It also makes you faster than slower (which i get from your reaction).
One final note: you cannot learn TDD from books. You might learn how the process is, but you really need to do/practice a lot.
Saying that i have to close the books and start writing production code is not really helping, though i wonder how you got to that in the first place.
Hello Bob,
I hope you are not a developer or you wrote this reaction just to provoke the professional developers that are taking their work seriously.
I have only one question for you. How do you guarantee your client (if you are a developer) that the code you will deploy actually works? In my opinion code that is not tested is not ready for deployment.
Hello Stephan
Thank you for this blog post.
I’m perplexed by the fact that your first test is named `anyLiveCellWithOneThanTwoLiveNeighboursDies` but the test doesn’t contain nor refer any cell. I surmise you chose to focus on neighbours, and the inference or relationship between number of neighbours and surviving. The thing that is surviving, you called it a `LiveCell`, but decided it’s not the focus of your test. Thus your first test tells the story of an attribute, a variable, standing alone inside a test class, not the story of a `LiveCell`.
I think your choice to omit the creation of a Cell is just that : your choice. It’s perfectly valid. However TDD doesn’t dictate that you should have made this choice, and that you call “Test First” a different choice, while yours can be called “TDD”.
I like these rules about TDD :
– Write production code only to make a failing unit test pass.
– Write only enough of a unit test to fail.
– Write only enough production code to make the failing unit test pass.
It involves writing production code. The fact that your production code was born inside your test class rather than in its own production class (or module or whatever) seems a moot point to me.
We all work on legacy code. All of us. It means that after a short while, what our tests refer to is some production code. What we have to change is production code.
Anyway, your post is interesting and intriguing. Thank you.
It has been a while this post was made, but good points nevertheless.
I think, looking back, the tests try to describe functionality without getting too deep into fixed code structures. Ie a “cell” is an idea. The idea that a “cell” has “neighbours” is also an idea, because how they are related is still not yet determined.
Hence, the test itself is pretty ‘dumb’ because it is, as you said, only a few lines checking an attribute being lower than a certain number. However, this is the very dumbed down version of TDD. You could take a look at Kent Becks TDD series for more examples how he goes about it.
TDD is usually done from the premise that you generate a certain design from the tests. However, it is (with what I now know) not without any thought beforehand. You surely will have some sense of what the system will do so an ’emerging design’ is not the case. In fact, in a strict sense for algorithms TDD can be quite good (if you don’t know how the algorithm will be), but for higher level abstractions there would be no sense in using TDD. Ie, if you are doing MVC, you don’t TDD MVC or the controller in that sense. Although it is wise to create Tests First.
About production code, I don’t consider test code different from production code. Code is code. I have to deal with it, regardless where it ‘lives’. The tests are made to keep checking the expectations of the system. And yes, you’re right, most code is legacy. In fact, you might even say everyone writes legacy code.
In the end, if you write tests or not, it all is part of ‘production’. The TDD example above is the case where your algorithm arises from the tests. Most tests become redundant and be removed. If you use baby steps/commits you get a very granular insight how the code evolved. In the end the ‘production’ code (the logic) lives in its production space, and the tests check the behaviour. If you have to make changes in the production code your tests will be the safety net.
Thanks for your response! 🙂