Test-driven development (TDD) is a programming technique in which test case is created first and then production code is created or modified so that it passes the test. The test case defines all the requirements that production code must implement. At first the test case will fail since production code under test does not exist yet (RED). Then production code is written so that the test case passes (GREEN). Lastly production code is polished (REFACTOR). If you want to read more about the concept, there’s a good article about TDD in Wikipedia.
TDD is widely used in software industry and it provides great benefits. However, it also has its downsides. I collected here some pros and cons developers generally encounter when practicing TDD.
Pros:
- TDD forces to develop architecturally better, modular code. In order to make code unit testable it needs to be modularized.
- Bugs can be found in the earliest phase, when writing code – cheapest to fix.
- Automated regression test suite is created during the development. Code is easier to maintain and refactor when tests cover you. Team members can modify each other’s code with more confidence.
- Test cases work as documentation of the code.
Cons:
- It takes time to learn a good way to do TDD.
- Slows down code writing.
- If the design changes a lot during the development, you need to rewrite tests – time loss.
- You need to write mockups to simulate operations like network connections, database access etc.
- Maintaining the test suite takes time.
Test-driven development provides benefits in large projects having many developers. Practicing TDD yields to better code architecture. It also gives confidence to modify other developers’ code thanks to regression test suite that is created during the development. These two benefits alone are essential in projects that will be maintained for a long time. It has been estimated that maintenance takes 60-80% of software’s life-cycle costs. The development phase costs are only about 20%. Looking at these figures, it should be acceptable that TDD slows down the development phase. Software that is easy to maintain will pay back in the future.
However, the feasibility of TDD depends on the context. For example TDD is not good for developing graphical user interfaces. It’s easier to find UI issues when using the application. On the other hand, if you’re developing safety-critical software such as aircraft flight control system or artificial cardiac pacemaker for a human heart, TDD is very recommendable to achieve high code quality.
Why some developers don’t use TDD?
If you read about TDD you’ll get quite clear picture that it’s a good idea to use it. But according to my field experience many developers are not practicing TDD. They may have used it for a while but then stopped. What’s the problem?
Perhaps the most significant reason is that it seems to cause extra work and makes development slower. Also even if you forget the testing perspective totally, implementing a complex software feature can be difficult task. At first you maybe don’t have clear idea how the feature should be implemented. Or it might be that you are not familiar with the platform. Sometimes it’s easier to start writing something and let the idea evolve gradually until you have something that roughly works. After that you can verify all the functionality by writing unit tests in test-last fashion and finalize the code. But if you need to begin by writing a test for a thing of which you don’t have clear picture, the process becomes even more difficult. At worst it can lead to mental block. TDD is here to help, not to make things more difficult. I think it’s totally fine to use test-last approach if it feels more natural in some cases. After all the main thing is working code, not the way it has been constructed.
What other reasons there are for not using TDD? Laziness? Deadline pressures? A strong testing team that will find the bugs anyway? It’s difficult to say, but I have seen that unit tests (and thus TDD) are often skipped.
Case study – my hobby app projects
A hobby project is a nice test bench for any development method. After day work you have only couple hours to push own project forward. You need to work smart and efficiently. If something in the development process seems to take more time than it pays back, you will eventually stop using it. You want to get things done and you don’t follow methods just because “this is the way it should be done”. In day job where developers get paid steady monthly salary it’s easier to follow methods 100% by the book.
Well, I have developed some hobby projects using TDD but mostly I have used test-last method. Why? The reason is that many of my projects were developed using technologies I wasn’t familiar with at first. It’s just hard to begin by writing tests in that situation. It’s easier to write something that nearly works and then verify all scenarios by writing unit tests. On the other hand, I have used TDD in some more complex apps. For instance the core engine of My Blogs was developed using TDD. During the high level design phase I realized that its engine is going to contain lots of calculations and logic that must work accurately. Thus I decided to construct the engine using TDD from start to finish. It was a good decision – I’m very happy about the code quality I achieved. Also as a consequence it has strong unit test suite that has saved time later in the maintenance phase.
Always when I use TDD my code has better structure. It’s more modular and easier to read. But the problem is that the price to pay for getting that quality is often too high for a hobby project. Since I’m the only one that maintains my own projects, the code doesn’t need to be 100% polished. I try to avoid over-engineering and thus use TDD only when I feel it’s needed.
I write unit tests for:
- Critical components that must never fail, like in-app purchases in mobile apps where user pay using their money.
- Components that contain complex logic that is clearly error prone.
I don’t write unit tests for:
- UI code. UI bugs can be seen quickly when using the application.
- Components that are exercised all the time and whose malfunction can be observed on UI easily.
- Corner cases that do not have fatal consequences if a failure happens.
- Components that require writing lot of stubs so that they can be tested. It’s faster to test them just by running the application.
- Simple components in which unit test don’t add any value – I want to avoid excess work.
Conclusion
The feasibility of TDD depends on various factors: development team size, software lifetime (=maintenance time), software type (low cost toy vs. aircraft’s flight controller), software complexity etc. Based on my experience, better software is constructed when TDD is used. There are fewer bugs and the code has better structure.
However, no method should be followed without thinking first. My free time app projects is a good example. The first apps I developed don’t contain unit tests but I have been able to add new features to them successfully with no side effects. On the other hand, some more complex apps were constructed using TDD and I’m sure it was the best way to achieve high code quality.
If you are not sure whether to use TDD or not, then use it. If you can’t come up with good arguments for not using it, then use it. If you haven’t used it before, give it a try. It’s the industrial standard.
Test-driven development is one defect-detection method among others like design reviews, code reviews, pair programming, integration tests and system tests. No single method is completely effective by itself – different methods find different kind of defects. The best quality is achieved using them in combination.