I've joined the Anti-IF Campaign

Saturday, 18 April 2009

Clean unit tests

Earlier this week, I read the chapter on unit tests in Clean Code. One of the things it stresses is that having clean unit tests is in some ways more important than having clean code, because unit tests themselves are prone to rot exactly as production code is. Yet, whereas rotten code can be refactored with confidence as long as it is supported by unit tests, rotten unit tests are an outwardly-spiralling liability that become progressively more difficult and time-consuming to maintain. Eventually, if left unchecked, rotten unit tests get dropped entirely, at which point, every subsequent change to the production code becomes a potential bug.

Well, forgive me father, for I have sinned. Seriously, I'm ridden with guilt on this one. It seems such a manifestly obvious point that I can't believe how thick I was being not to realise it before. I always just kind of hoped that my unit tests were fire-and-forget tools, smart weaponry that needn't be reconsidered once written. At times, of course, I've had to modify tests to account for behavioural changes in my code or whatever, but I've always done it in a thoughtless fashion, regarding it as a distraction from my actual work rather than important work in its own right.

The area in which I'm most culpable is 'single concept per test'. The anti-pattern I've tended to use in my unit tests is to write a test for a given method, then continue to test all variations of parameters and entrance conditions to that method in the same place. But, of course, each of those variations should be a separate test in its own right. It has also confirmed for me that I'm doing test-driven development wrong. For some time, I've suspected that I'm not taking small enough steps, and I see my dirty unit tests as another symptom of that. They are part of my coding phenotype, I guess, with incorrect TDD in my genotype.

So, anyway, that's the diagnosis. The prescription for cure, I think, lies in the build-operate-check pattern and perhaps adhering to a given-then-when naming convention (ripped off from Dan North's behaviour-driven development). I shall also experiment with limiting each test to a single assertion, although I'm not convinced enough about that to get dogmatic about it.

3 comments:

  1. Hi Phil,

    Saw your blog link when I clicked on a linked-in update. I'm well impressed! I don't have the talent for writing a blog like you have done but I'm interested in a lot of this stuff and will probably have a good read of this later on.

    TDD - the project I'm working on is supposed to use that. It seems to have fallen by the wayside as the amount of mock classes became too large. Most have been commented out since a major re-design of the system.

    I can't begin to think about the quality of the unit tests - we're really lucky if there is one! Still, I should think about the quality of the ones I write.

    Cheers.

    Peter.

    Good skills!

    ReplyDelete
  2. Hey Peter, how goes? Most of the TDD and XP stuff is pretty new to me too, it is only over the last year or so that we started getting stuck into it at work.

    It's a shame most of your unit tests got commented out during that re-design. I'd have thought it would be worth the extra effort to update the tests, no matter what. Otherwise how do you know that everything still works?

    ReplyDelete
  3. Hi - I'm OK. Working in Maidenhead.

    Unit tests - yeah - you'd think so. We are in a major panic to make a deadline despite our project plans and "buffer zones".

    We could have updated the tests but the fact that they needed updating showed they had too many dependencies. The new plan is to use unit tests for classes with few or no interaction with other classes and to use "integration testing" for anything else. We use FitNesse for that.

    What's actually happened is that the unit tests have gone (a lot of them) and nobody has time to write FitNesse tests. Consequently, I have seen bugs appearing and re-appearing.

    In the plug-in that I work on, I have re-done the unit tests so that they don't use mocks of the dependent classes but use the actual classes. So they're sort of not really unit tests now! What a mess!

    ReplyDelete