Why would I want to write a game of noughts and crosses? Well we were giving a presentation on Test Driven Development (TDD) to Eurotherm’s R&D department. To do such a talk, one really needs to write some tests, and if one writes tests, one really needs to write code to pass those tests. Thus why I was sitting there, with people watching me write code. The presentation went very well, despite it being the first time either myself or Laurence had attempted such an ambitious task. Now part of the reason it went so well is because I have learned that essential ingredient to any presentation: rehearsal. I practised writing the code about ten times before the day. But I also think there was another ingredient to the success: TDD itself.
So what is TDD and why do I make such bold statements about it? I produced a short five minute overview of the topic for the start of our presentation that I’ve reproduced here, which hopefully answers that question.
I’m sure you all know the traditional way of developing software:
- Define the requirements
- Create one or more specification documents that try to capture all the requirements and decompose them into a detailed design ready for implementation
- Implement the specification. This involves writing reams of untested code. Hopefully it compiles. Maybe it even has a simple test harness associated with it. That’s about it though.
- Start testing. Find hundreds of problems. Re-work the code. Bodge it a bit. Retest. Eventually ship it.
- Maintain. Yeah, right! It’s a disaster, the code breaks when ever we try and maintain it and it takes a lot of time and effort to ensure that when we ship updated product, those broken bits are fixed.
Surely there must be a better way? Well there is and it’s called Test Driven Development. The key to understanding why it’s better is the word “driven”; the tests drive the development, not the other way around. That way, when you have finished developing, you have finished testing too!
Now most people baulk at the idea of doing TDD because of this idea of tests first. This is because they mistakenly assume that they must write all of their tests, then write all of their code.This is wrong. It is important to emphasise the point, for not only would writing all your tests first be mind-numbingly boring, it would be an integration nightmare too. On a large project, you might develop a few thousand tests which you would then have to painstakingly integration with many thousands of lines of code. This would be horrendous.So the solution is to do the tests one at a time.
Now this is where TDD really shines: one test at a time. So you write some test code and then run your test. The test should fail at this point as you do not yet have code that passes it. So you write some code and re-run the test. Your test now passes. Now the clever bit comes in to play. You write a second test, which fails of course. This is key. It is really important that you only wrote code to pass the first test, not to pass tests you haven’t yet written. So then you refactor your code so that it passes both the first test and your new test. So why is this clever? Because you just refactored your code, that’s why. Refactoring is normally a word that brings anyone who has had to make changes to someone else’s code out in a code sweat, Any change introduced is likely to unforeseen (and normally untestable) consequences. But with TDD, you can refactor with confidence, as you have a full set of tests for checking it still does everything it used to do.
(Image copied from Ashutosh Nilkanth’s TDD Article)
Time progresses, more tests are written, more code is written to pass those tests. Then one fateful day arrives: integration time. This is normally another time that developers dread. The code will fail on the first attempt at integration and then the blame game starts: “well it can’t be my code; it must be yours”. Once more, TDD can come to the rescue here. When a software system is made up of more than one component, those components have interfaces. Those interfaces are part of the requirements and so those interfaces will have a set of tests written for them. As long as both components are developed using TDD, the interfacea between them are tested in advance. This tends to mean that integration just works. My own personal experience recently has been that the two components integrate – first time. Sure there is the odd niggly problem, but that was caused by poor definition of the requirements. Reassessment of the requirements, followed by a modification of tests and refactoring to fix those tests, solved the problem each time.
So in summary, TDD offers three key advantages to the developer:
- A good set of tests is written whilst developing the code
- Code that can be refactored/ maintained with ease is written by default
- A component developed with TDD is easy to integrate with other TDD-developed components
Now let’s be honest, if you don’t think that is a great set of advantages, you have never developed a real software system. As I said in the title, Test Driven Development is Great!
Want to know more? I’ll post a bunch of links for TDD tomorrow, so watch this space…