Yes, writing automated tests can be laborious. I routinely observe myself writing every bit as much testing code as production code. However, I have found that writing code might take longer with TDD, but software development as a whole (design, code, test, rollout), is faster. After all, the real goal isn’t slapping out code, it’s getting working code to the business.
Here is where I think TDD cuts time in software development:
- Design. TDD is a low level design technique. It wasn’t an overnight conversion, but since I’ve started using TDD I spend much less time with UML and other upfront design techniques. The feedback cycle between “I wonder if this design will work” to validating the design is much shorter.
- Debugging. As your ability to do Test-First unit tests improves, your usage of the debugger goes way down. I’ve definitely seen this in my coding over the last two years, and I’ve read may similar accounts.
- Fixing bugs. Bugs can be fixed more rapidly because the unit test coverage can detect regression failures before a tester does.
- Testing. Fewer bugs get through to the testing team. By doing test-first, you have to take testability into account in the design of your application. Non-coding architects doing upfront design may have no incentive to promote testability. From painful experience, if you use any kind of test coverage tool like NCover or Clover.NET, you’ll find a nearly one-to-one relationship between code with poor unit test coverage and the code that spawns reported bugs.
Learning TDD was difficult, especially for someone like me with a bit of background in UML modeling. It hurts at first, but TDD does get easier with practice. There is far more collective knowledge and experience with TDD in .NET today than there was a couple of years ago. I’m trying to use my “TDD Design Starter Kit” blog series as a start for my group.
Maximizing the TDD Advantage
TDD can be done with any process or organization, but it is more effective and valuable in an iterative and incremental process openly using emergent design. Every developer (or pair) needs to have some control over the fine grain design details. Spec coders need not apply.
Refactoring automation tools help tremendously. ReSharper (and Whidbey?) can generate method stubs for missing methods. That little piece of functionality made writing tests first much easier mechanically.
Continuous Integration (CI) is a natural extension of TDD. The TDD/CI combo is very powerful in reducing friction between development, testing, and production.
There are obviously some types of code that are much easier to verify manually instead of an automated test. In these cases you might compromise and get by with a manual test. A lot of effective TDD design is minimizing or isolating hard to test functionality to maximize the testability of the application as a whole. The Model-View-Presenter pattern (“Humble Dialog Box”) is a good example of this. I’m a big fan of using mock objects for external dependencies.