Bureaucratic tests
The TDD cycle should be fast! We should be able to repeat the red-green-refactor cycle every few minutes. This means that we should work in very small steps. Kent Beck in fact is always talking about “baby steps.” So we should learn how to make progress towards our goal in very small steps, each one taking us a little bit further. Great! How do we do that?
Example 1: Testing that “it’s an object”
In the quest for “small steps”, I sometimes see recommendations that we write things like these:
it("should be an object", function() {
assertThat(typeof chat.userController === 'object')
});
which, of course, we can pass by writing
chat.userController = {}
What is the next “baby step”?
it("should be a function", function() {
assertThat(typeof chat.userController.login === 'function')
});
And, again, it’s very easy to make this pass.
chat.userController = { login: function() {} }
I think these are not the right kind of “baby steps”. These tests give us very little value.
Where is the value in a test? In my view, a test gives you two kinds of value:
- Verification value, where I get assurance that the code does what I expect. This is the tester’s perspective.
- Design feedback, where I get information on the quality of my design. And this is the programmers’s perspective.
I think that in the previous two tests, we didn’t get any verification value, as all we were checking is the behaviour of the typeof
operator. And we didn’t get any design feedback either. We checked that we have an object with a method; this does not mean much, because any problem can be solved with objects and methods. It’s a bit like judging a book by checking that it contains written words. What matters is what the words mean. In the case of software, what matters is what the objects do.
Example 2: Testing UI structure
Another example: there are tutorials that suggest that we test an Android’s app UI with tests like this one:
public void testMessageGravity() throws Exception {
TextView myMessage =
(TextView) getActivity().findViewById(R.id.myMessage);
assertEquals(Gravity.CENTER, myMessage.getGravity());
}
Which, of course, can be made to pass by adding one line to a UI XML file:
<TextView
android:id="@+id/myMessage"
android:gravity="center"
/>
What have we learned from this test? Not much, I’m afraid.
Example 3: Testing a listener
This last example is sometimes seen in GUI/MVC code. We are developing a screen of some sort, and we try to make progress towards the goal of “when I click this button, something interesting happens.” So we write something like this:
@Test
public void buttonShouldBeConnectedToAction() {
assertEquals(button.getActionListeners().length, 1);
assertTrue(button.getActionListeners()[0]
instanceof ActionThatDoesSomething);
}
Once again, this test does not give us much value.
Bureaucracy
The above tests are all examples of what Keith Braithwaithe calls “pseudo-TDD”:
- Think of a solution
- Imagine a bunch of classes and functions that you just know you’ll need to implement (1)
- Write some tests that assert the existence of (2)
- [… go read Keith’s article for the rest of his thoughts on the subject.]
In all of the above examples, we start by thinking of a line of production code that we want to write. Then we write a test that asserts that that line of code exists. This test does nothing but give us permission to write that line of code: it’s just bureaucracy!
Then we write the line of code, and the test passes. What have we accomplished? A false sense of progress; a false sense of “doing the right thing”. In the end, all we did was wasting time.
Sometimes I hear developers claim that they took longer to finish, because they had to write the tests. To me, this is nonsense: I write tests to go faster, not slower. Writing useless tests slows me down. If I feel that testing makes me slower, I should probably reconsider how I write those tests: I’m probably writing bureaucratic tests.
Valuable tests
Bureaucratic tests are about testing a bit of solution (that is, a bit of the implementation of a solution). Valuable test are about solving a little bit of the problem. Bureaucratic tests are usually testing structure; valuable tests are always about testing behaviour. The right way to do baby steps is to break down the problem in small bits (not the solution). If you want to do useful baby steps, start by writing a list of all the tests that you think you will need.
In Test-Driven Development: by Example, Kent Beck attacks the problem of implementing multi-currency money starting with this to-do list:
$5 + 10 CHF = $10 if rate is 2:1
$5 * 2 = $10
Note that these tests are nothing but small slices of the problem. In the course of developing the solution, many more tests are added to the list.
Now you are probably wonder what would I do, instead of the bureaucratic tests that I presented above. In each case, I would start with a simple example of what the software should do. What are the responsibilities of the userController
? Start there. For instance:
it("logs in an existing user", function() {
var user = { nickname: "pippo", password: "s3cr3t" }
chat.userController.addUser user
expect(chat.userController.login("pippo", "s3cr3t")).toBe(user)
});
In the case of the Android UI, I would probably test it by looking at it; the looks of the UI have no behaviour that I can test with logic. My test passes when the UI “looks OK”, and that I can only test by looking at it (see also Robert Martin’s opinion on when not to TDD). I suppose that some of it can be automated with snapshot testing, which is a variant of the “golden master” technique.
In the case of the GUI button listener, I would not test it directly. I would probably write an end-to-end test that proves that when I click the button, something interesting happens. I would probably also have more focused tests on the behaviour that is being invoked by the listener.
Conclusions
Breaking down a problem into baby steps means that we break in very small pieces the problem to solve, not the solution. Our tests should always speak about bits of the problem; that is, about things that the customer actually asked for. Sometimes we need to start by solving an arbitrarily simplified version of the original problem, like Kent Beck and Bill Wake do in this article I found enlightening; but it’s always about testing the problem, not the solution!