As a developer with 10+ years’ experience, it’s likely surprising to many of you that I have never been on a project that truly used TDD.
Don’t get me wrong. I have been on projects where some of us wrote some unit tests occasionally. I have also been on projects where we wrote integration tests, which proved to be very valuable.
- I’m working with older code that was not written to be testable
- I have a tough deadline and don’t have the time to write tests
- The developers on the project are new to TDD/Unit Testing/Mocks and the learning curve is too steep
- A client/PM that is skeptical of the value TDD can bring for the time and money it costs
- A team is dealing with 100 new technologies as it is and really doesn’t need another methodology/framework/ideology/software/technology to add to the mix
Here are the lessons I’ve learned:
- Design code better: TDD doesn’t mean you “must” test first, only that it is a good idea. When I force myself to write tests first, I also force myself to design my code before I start writing it. This isn’t always possible but when it is, my code usually comes out easier to read and requires less refactoring.
- Over complicated tests = over complicated code: in If I am writing a test and the test is getting incredibly complicated with 10+ mock objects and pages of setup code, it is extremely likely that the class I am testing is much to complicated. Complicated tests = complicated classes. With a bit of refactoring, my class become simpler and easier to read, and my tests do too.
- Simple, testable code is more likely to focus on a single responsibility. If your code isn’t testable, it is likely you didn’t design it right. It also implies that you didn’t write your tests first. Often I believe I’ve written a good piece of code and go to test it after, only to find that my class can’t be tested easily. Often the class I am testing violates the Single Responsibility Principle. When this happens, my code isn’t as testable and I need to put the second responsibility in a second class. This way I can mock out the functionality and properly test my classes.
- Tests = documentation: Some people put JavaDoc on their public methods, others don’t. If you want to see what the author(s) of a method were trying to do, there are few better ways than to look at their tests. Tests document code.
- Debugger = wasted time (often, not always). Prior to using TDD, I tested all of my code using a debugger in a real instance of a running application. This often meant wasted time on deploys, restarts and navigating to a point in the application where my code actually ran. With Unit tests, your code is isolated and tested instantly. This saves an incredible amount of time.
- TDD finds bugs too: The obvious result is that TDD finds more bugs. I am continually amazed at how often my tests fail, when I believe that my code is correct. More comprehensive testing means more bugs found earlier. This results in less bugs in production, which results in less time/money spent on maintenance.