When you start using Test Driven Development, it is easy to get so excited about having regression tests that you miss the point of using tests to drive the actual development process. While regression tests are a nice benefit from following TDD, they really aren’t the main point. TDD is about letting the design of your code be driven by the following cycle:
- Write a simple test that will fail.
- Write simple code to make the test pass.
- Refactor to improve the existing functionality.
- Retest to make sure functionality is still the same.
- Back to step 1.
The goal is to follow a discipline that will produce code that is simpler and more robust than what would emerge if you were to focus on a larger part of the problem.
You cannot use TDD successfully if you believe that a line of code should only be touched once. TDD requires you to do a lot of writing and rewriting. You may write code that makes your first test pass knowing that it will be rewritten when you add the second test. At first this can be frustrating. Why would you write code that works for test A knowing it won’t work for test B that you are going to write 90 seconds later? Why not go ahead and do it right the first time?
The reason is simple. You want to minimize the amount you have to think about at any given time and also minimize the amount of time that your code is in a broken state. The more things you try to do before getting your code back to where it runs, the longer the code exists in a state where it won’t run correctly. If you add a method that simply returns a hard coded value, it might seem like you’ve accomplished nothing in that step of getting the test to pass. However, you’ve made several decisions about the new method: Where should I put this method? What should I name it? etc. When you add another test that forces your method to actually do something intelligent, you’ll still be working with a smaller set of changes than trying to do it all at once.
Once a test is passing, that doesn’t mean you are ready for the next tests. It means you are ready to refactor. You are now in a working state so you can change things and immediately tell if it changed the way the application works. Once a test passes you need to consider if there are any refactoring steps you can take to clean up what you have so far. Have you named things in a way that will be clear to others? Can you break some of your code out into a helper method to make it easier to understand?
This is probably the most important part of TDD—taking an application that is passing the existing tests and improving the code while leaving the functionality unchanged. If you aren’t trying to do this before each new test you write, you aren’t doing TDD.