TDD Is A Tool, Not A Religion
People talking about Test Driven Development often split into two camps: fundamentalists, preaching no line of code should be written without a test, and deniers, refusing to use TDD at all.
[I will use TDD as an alias for test-first development here.]
Yesterday Gregor asked me how I am handling TDD in my AngularJS projects. He added:
I think test-first development is only justified for real business logic, services and [AngularJS] directives. Testing afterwards is okay for controllers and should be done by integration tests. At least that approach worked out in my desktop world (with WPF).
I will answer in a more general way here, because AngularJS is just one of the frameworks I am working with.
My Experience So Far
Six years ago I had the time and the budget to try test-first development extensively for the first time in a small project. I forgot the exact coverage I achieved with my almost 500 unit tests, but the result was pretty good. Today the application (an ASP.NET MVC web application) is still being used after all the years and produces maintenance expenses of, strictly speaking, zero.
On the other hand I have done much more projects where we went mainly without a test-first approach, and none of these projects failed, too. At least not because of test-coverage being too low ;-).
Costs Of Test-First
People arguing that code written with a test-first approach results in a better overall application design may have a point.
But that often depends of the point of view, too, because to get things testable you need some more infrastructure and often a higher level of abstraction for even simple things. If you’re doing all that just for the sake of testability that may be okay for complex applications but a bit overambitious for simple ones.
Another drawback is maintenance costs of your test suite. I sometimes caught myself by getting a bit messy with my tests because I just wanted to get shit done. Refactoring a lot of “mediocre” tests afterwards because of a larger design change within the application then quickly becomes annoying.
And a third point to mention is the cost-benefit ratio. By covering simple things like a ASP.NET MVC controller action that just returns a view or a AngularJS directive that’s just manipulating some DOM element, the value you get in return for your automated tests is far too small compared to the huge effort you have to make instead of just testing it manually.
I think it was Steve Sanderson who once stated on his blog, that he would only test things he couldn’t scan easily in a few seconds for finding a bug. And I think he’s right.
So, How Do I Handle TDD?
Like always, it depends. But despite some good experiences with widely used test-first development I don’t think that this approach justifies the huge efforts in most cases.
So I am using test-first today mainly in two scenarios:
- Algorithms and other complex or critical parts of applications in common, for example validation.
- Parts of applications that rely on dependencies I don’t know yet or that I don’t trust. For example a new database provider or an third-party API.
One Last Thing
Regarding the point of refactoring large applications and as a consequence thereof refactoring huge test suites as well: In the discussion of Martin Fowler, Kent Beck, and David Heinemeier Hansson, titled “Is TDD dead?”, Fowler mentions at some point that he isn’t mocking dependencies.
That may violate the rules of “unit tests” as they may become more like “integration tests”, but it could help to keep things simple in your test suites.
What do you think? Drop me a line and let me know!