I saw a comment on Slashdot earlier which said “not everyone has the time for test driven development…”.
I’ve come to enjoy TDD naturally, not because I’ve been pressured into it, not because I care about fashion, but because after a while you just get bored of writing code that doesn’t work, but not finding out for two weeks, and then spending hours debugging it because you can’t quite remember what it does or why. It seems ludicrous to me that you could say you don’t “have time” for test driven development, because that implies you do “have time” to investigate and fix mistakes in things you have to re-learn, which is almost certainly a longer and more painful process than simply writing it properly the first time around.
A test is just a programmatic encoding of a requirement. It’s an automated way of being able to check you’ve solved a requirement. You are presumably going to have to check this somehow anyway… Granted, some tests are fairly convoluted to set up. This is true. They can also be inflexible. Okay. But you don’t lose time by having a strong argument that your code works as you write it. You lose time by not really knowing, apart from setting up some roundabout mouse clicking ritual that sort of tests 20% of the code paths you’ve just added, and then two weeks later finding out some of them are totally borked but having no idea where the strange behaviour you’re suddenly encountering is actually originating from.
It has some other great outcomes:
- If you write a test before you write code, your aim becomes much different to standard programming – suddenly, you’re trying to find the smallest amount of changes to the codebase that will make the test pass. It’s a very different way to look at things. It reduces code bloat. Also, since you are calling the new API in the test, you are suddenly thinking “how do I need to call this?”, which can present a different answer to “how should I expose this?”, and the answer to the former is almost certainly a better way to lay out your API.
- As a side effect, you build up a great big library of tests. This is awesome when you’re trying to fix a bug in old code; as you know, any change you make that fixes that bug might have unforeseen consequences. If your tests are good, any changes introduced that are incompatible with any other requirements should get flagged instantly. To some extent, this lessens the amount you have to understand the code you’re editing because you get some automated checks in place of having to be awake.
it has downsides of course
- some requirements are UI centric and it’s hard to test a UI programmatically. Hopefully you can test the underlying implementation easily, though.
- Some requirements are hard to encode as a neat set of tests. Sometimes scope is vast. You can’t properly unit test f(x) => x2; it has infinite inputs. You just choose a few inputs and any edge cases and hope for the best.
- You do have to write code to be testable. You’ll see what I mean when you try. But testable code is (more likely to be) well structured code, so this isn’t really a ‘cost’.
tl;dr: test your code.