In this article, I describe the concept of Test Driven Development. It is a very important part of your Programmer’s Toolbox. It will help you write better code and have less stress maintaining and extending it.
What is Test Driven Development?
Test Driven Development (TDD) is a development technique that has several aspects:
- You guide your software development with unit tests. This means that before writing any code for your program, you write a test first. This test will tell you if the code that you are about to write does what you want it to do.
- You develop your software in small increments: You write just enough code to make the test that you have written pass. The tests you have written before make sure that you have not broken the existing functionality.
- You keep your code readable and maintainable by cleaning it up after every increment.
Why would you do TDD?
This sounds like a lot of extra work. In addition to writing the code of your program, you also have to write test code. And you do it rather slowly, as TDD uses small iterations. So what are the benefits of doing it this way?
Maybe you know this situation: You want to add a cool new feature, have an idea of how to do it and start working on the code. You add a new class here or extend a method there. But after a while you are stuck, running out of ideas how to get out of this mess. And you start all over again. The reason for this waste of time is that you were not clear enough about what you wanted to do right from the beginning. It is much better to think about what the code should do upfront. And this is what you do when you write a test first. The task of a unit test is to check that a piece of code does what it is intended to do. In order to write such a test, you have to be clear about what your code is supposed to do. Believe me, this is the biggest time-saver in programming!
When you do TDD, you write tests. One after another, these tests pile up. After a while, you will have a considerable number of tests. These tests will be your safety net for the future. Because sooner or later there comes the day that you have to change your software, either for extension or for bug fixing. Your software has become quite big. Each and every small change could break some functionality in a way that you cannot predict. This is where your tests act as a safety net. You can run them after every change you make. They will show you whether you broke anything. You want this safety net in place by the time you have to do these changes. Otherwise you start creating it under pressure. TDD gives it to you for free.
After writing a unit test and making it pass, you refactor the code that you have written so far. If you do this right, you will end up with code that is very easy to read. This will help tremendously when you have to come back to this code later. And your colleagues will thank you too.
TDD in Detail
At the heart of TDD lies the TDD Cycle. It can be described with just three words: Red, Green, Refactor.
Before starting this cycle, I suggest you make a list of all the functionality that you want to implement. Then you can pick one item from the list and start the cycle.
At the beginning of the TDD cycle you write a new test for your new functionalty. You think about the next bit of functionality of your software. You put these thoughts into a new test.
If the class or method that your test calls does not yet exist, your test will not compile. So you have to write just enough code to make the test compile.
Then you run the test. It has to fail at this stage. The test runner will show this test in red. It is very important that you don’t miss this step! You have to run the test, and it has to fail. If the test didn’t fail, it would mean that it is already fulfilled by the current state of the system. Thus it will not cover the new functionality that you are about to write in the next step.
It is also very important that the error message that you receive in this step is clear. It should show you the real nature of the error. If the error message is vague, it will not be much of a help in case this error ever occurs in the production system. So make sure that you add error messages to the expectations in your tests.
You can think about it this way: If you add a new room with a smoke detector to your house, you will at least test the detector once before you trust it. You would put some smoke into that room and expect the fire alarm to ring. If the alarm does not ring, then the smoke detector is useless. And if there were an alarm, but it did not show the room in which the fire broke out, then it would take the firefighters much longer to find the source of the fire.
Now that you have your test in place, the next step is to make it pass. It will be shown green in the test runner. You do this by adding just enough code to your system to make the test pass. This means that you go ahead one tiny step at a time. Resist the temptation to add the complete implementation of the method that is being tested. Just add enough code to make the current test pass. Add all the other functionality to your backlog.
Once the test is green, it should stay green through all the following cycles. If it becomes red again, it is a sign that you broke something. Then you have to fix it right away. Your safety net is in place now.
Now you have a test, and you have added the functionality to make this test pass. Before you go on writing the next test, you should clean up. The goal is to keep the code clean and maintainable all the time. This requires some work, but it is better you do it in this step instead of one big refactoring later on.
So in this step you reduce duplication in your code. You extract any magic numbers that you have introduced or replace them with the intended calculations. If your class becomes too big, it might be a sign that it has multiple responsibilities. You might then want to break it into several classes. Thus you end up with well formed classes that are easy to understand and to modify.
After every refactoring step, you run the test cases again to check if you broke anything. If so, you can easily revert to the last step.
It is also important that you keep your test code clean and tidy. Consider it as important as your production code! So take care of reducing duplication here. And keep it readable and understandable all the time.
After completing the cycle, you can cross this item from your list. Then pick the next item and start the cycle all over again. Keep developing one item at a time.
As soon as you have all items crossed out, you are done with the functionality. You have extended your system and left it in a clean, maintainable state together with a safety net.
Now that you have learned about the TDD concept, you need to start practicing it.
First, you need to learn how a test framework works. In addition to that, will have to learn about Test Doubles.
To bring all of this together, check out my example project in which I develop a complete calculator with Test Driven Development.