The T-Files


Wed, 09 Mar 2005

Test-driven Development

I have heard about code testing methodologies at university, and even played around with JUnit at the time, but I have not really got into it, and it has so far had little impact on my programming style. I remember attending a seminar in which testing was likened to brushing teeth: You can do without it, but in the long run, it is much better if you do it. As my dentist can confirm this reasoning is somewhat lost on me.

But things have changed. A month ago, I had some Java functions of mine returning embarrassingly incorrect result, which could have been easily detected (and fixed) by myself first had there been a suite of unit tests. In fact, this was a text-book situation for unit testing: Side-effect free functions that transform some input data into some output determined solely by the input. So I started pulling out JUnit again, and in the process did some reading, found out about Test-Driven Development, and got hooked. In TDD, testing is not just some peripheral task for the developer, but the centrepiece of the development process. You end up writing test before you write code, and the test cases are likely bigger (in terms of lines of code) than the modules they are testing. There are a number of pleasant results of TDD:

  • You get automated regression tests. Whenever you change something, chances are that you broke something (very likely something else, code beyond a trivial size has interdependencies that become hard to keep track of). After running your test suite, you can be sure that everything that worked before still works.
  • It is a great debug tool. You usually have unit tests for all programming units, so you can track down bugs to the module that causes it. Someone said that whenever you would want to insert a "print" statement into your code, you should rather write a test case now.
  • It can be used for bug tracking. If someone files a bug report, you can turn it into a test case. Until the bug is fixed, you application will fail its test suite.
  • If you write tests before code, the test cases can also serve as a statement of intent. They show where you want to go with your project.
  • Test cases help to clarify the interface your code provides to its users. Especially in the absence of a formal specification they can act as a reference for how things are supposed to work. In the process of writing these test, you can often discover problems in the specs and fix them early on.
  • Similarly, tests contain code examples and thus make for good documentation (especially since they are more likely to be up-to-date than the real documentation).
  • If you start by writing tests, and then set about to make the code pass these tests, the test reports are a measure of progress.
  • Coding with testability in mind encourages reusable components. You now have to structure your code in such a way that parts of it can be tested. So it is no longer okay that you have convoluted internal call sequences without meaningful intermediate results. Significant parts of your application must be able to work outside of its primarily intended deployment environment. Parts of it will probably be replaced by mock objects, so you have to (in Java terms) make use of interfaces rather than rely on specific classes).
  • Test suites can help verify that deployment was successful. Just because the code runs on your development machine does not mean it works in the production environment. This is why all CPAN modules (which can depend on other modules, or native libraries, or specific Perl or OS versions, or a C compiler, or environment variables, or ...) include self-tests, that are part of the installation process. Even in the supposedly Write Once, Run Anywhere world of Java, different JDK or application container version, or just missing/incompatible JAR files can be a problem.