Test Driven Development (TDD) is one of the cornerstone Agile techniques that is relatively easy to learn from the bottom-up. A developer can add TDD to his repertoire independently, without help from the team or the organisation.
Contrary to first impressions, TDD is not about testing, it is about designing (read more about Agile design in a previous blog entry). As it is with many development processes, the input to TDD is a specification and the output is software. It differs from other processes in that the automated unit tests is the essence of the produced software.
The benefits that TDD brings become reachable when two assumptions are in place. The first is that unit tests are created, and the second assumption is that the TDD design approach is fully adopted. Here are some benefits you can look forward to:
- Units tests allow you to verify that the software remains functionally correct after changes are made.
- Running these tests help identify some non-functional issues soon after it was caused. For example, an unexpected performance glitch would be picked up when the tests take longer than usual to run.
- The design is likely to be better because the code is developed for at least two interfaces (the tests is one and the calling module is the other). The developer designs code that is easy to use because he is the first user of his code.
- The test set has a high level of coverage when the work is done. This increases the level freedom developers have to refactor code. Refactoring improves the design over time.
- Unit tests illustrate many use cases to developers and as such the tests become an active form of a specification document. Developers look up the code in a test to learn how to use a feature, and also read through test code to gain a deeper understanding of the design.
As a development process, TDD is very simple - it consists of only four basic steps:
- Create a RED test. Your first task is to create a unit test that fails. Writing this test confirms your understanding of the specification and it highlights exception conditions. You start forming an opinion about the classes and new methods you'll need. During this TDD step, you should implement simple stubs that deliberately fails your test. Keep the scope of the test very small - cover no more than one case of the specification. This step is done when the new test fails for the right reason, and when all old tests succeed.
- Quick, get the test GREEN. Implement those stubs you created to get the test to pass. Use the simplest approach possible. Be sure to focus only on getting the test to pass - do not predict alternatives or allow for other cases. If you do, these cases will not have tests associated with them. This step is complete if and only if all unit tests succeed.
- Refactor to improve. The main idea at this step is to revisit the implementation and make it more elegant. Now that you have a running test harness, you can confidently change the code towards a better design. But, do not get carried away, the principles of KISS and YAGNI should always be kept in mind. If you feel the urge to add another use case from the specification, it means you are ready for the next step. The first time you go through step (3), there may be little or nothing to do here. As the system grows and more cases are supported, this step becomes more interesting.
- Refine and repeat. Take a new use case of the same feature and restart the process from step (1) using that case as specification. If all cases are covered, start with the next feature. If all features are covered, go get a beer: your application is complete.
Clearly, the TDD process is incremental and each increment must be kept simple by choosing a small scope. Each cycle should take two hours to implement at most and ideally no more than a few minutes.
TDD is fine when you start a new system, or when you add a new feature, but what about bugs? Even if you are not doing greenfield development, every bug presents a grand opportunity to exercise those TDD muscles. The process for apply TDD for a bug fix should be along these lines:
- Create a RED test. Start by writing a functional test that reveals the bug. On an old system, creating the first test is likely to be more difficult than it sounds, but after this work, a basic framework will be in place to write more tests easily. While hunting down the bug you may create some tests that succeed. Leave those tests in place and keep on digging. This step is done when you have a failing test.
- Quick, get the test GREEN. Now that one failing test is in place it is time to fix it. During this fix-it-quick period, you may develop a theory or two. Write tests for these, either debunking or confirming each theory. At some point you'll find the real cause and all your test will pass after you fix it!
- Refactor to improve. You now understand the problem a bit better, and you might want to refactor your solution. Remember to avoid the refactoring of areas in the code that are difficult to test. Refactoring is good, but it is no excuse for messing up working code.
- Refine and repeat. It is now time to think about alternative cases that may fail. Write tests for these and when one fails, go to the first step. If all cases are covered, its time for beer: you deserve it.
In short: Get TDD into your toolbox!
0 comments:
Post a Comment