Adventures in Pure Test-Driven Development
In a previous post some time back, I had expressed some skepticism about TDD as a design practice. I talked about test-driven development and its relationship with prototyping and the “make one to throw away” concept.
Since I’m not one ever to believe that I’ve arrived at the optimal solution, I’m doing another round of pure TDD to see if I can ‘refactor’ my process a little and find improvements in efficiency and/or code quality. As I’m doing this, I’m finding that I’m a little more proficient in the TDD concept than I was in previous attempts, but I’m still not convinced that TDD is a golden hammer appropriate for all solutions, all the time.
I’m going to make a list of pros and cons here as I work, both for myself and for posterity. Once I’ve compiled the list, I’ll wait a little bit, reflect, and write a conclusion on it (so yes, the part at the bottom will be written two or days removed from what you’re reading right now).
- Using mocking frameworks (Moles and Moq), maintaining 100% code coverage is effortless
- Tests tend to be more meaningful for your use cases than retrofitted ones.
- Better gaurding against regressions
- TDD is better even than I remember at forcing me to think through various scenarios of method control flow and object state. I’m having far fewer cases of, “oh, I didn’t consider scenario X!”
- Cuts down on potential for dead code. I’m not doing things like declaring a setter because I’m already declaring a getter and I might as well. The rules of the process don’t let you code that until you have a test for it.
- Tends to help generate useful and well-conceived abstract utility classes (such as a no-exception wrapper for string splitting that I wrote) that can be reused elsewhere.
- Larger design concepts do not seem to be emergent the way they might be in a more shoot-from-the-hip approach. Figuring out when you need a broader pattern seems to require stepping out of the TDD frame of mind
- There is no specific element of the process that naturally pushes me away from the happy path. You wind up creating tests for exceptional/edge cases the way you would without TDD (I guess that’s not strictly a con, just a lack of pro).
- It becomes easier to keep plodding away at an ultimately unworkable design. It’s already hard to throw work you’ve put in away, and now you’re generating twice as much code.
- Correcting tests that have been altered by changing requirements is magnified since your first code often turns out not to be correct. I find myself refactoring tests as often as code in the early going.
- It has the potential to slow development when you’re new to the process. I suppose one might look at this as a time investment to be paid off when you get more fluid and used to doing it.
I like aspects of this process, and I like it enough that I’m going to keep making stabs at it to find exactly how it works best for me. In my earlier post, I mentioned that I’d do prototyping without tests and then do TDD in earnest when I started writing my “for-real” code. I think I’m going to try to phase it in increasingly to my prototyping as well.
My biggest issue is my perception that TDD removes more emergent macroscopic principles of design. There is no straightforward refactoring I’m aware of that leads to a MVVM pattern of development or to using something like a flyweight. So it’s going to be up to me and my refinement of my own process to reconcile Uncle Bob’s three laws of TDD with sophisticated design concepts.
Of course, I’d be interested to hear, in comments, anyone’s take on how to reconcile these two concepts. Perhaps I just haven’t been at it long enough to see a test that I can write that demands a refactoring to a more complex pattern. Or, perhaps I’ve become so used to recognizing good fits for patterns ahead of time that it’s no longer part of a refactoring for me, but the original ‘factoring’. In that case, I suppose I’d have to retool my methodology for arriving at design patterns.
Regardless, though, it should be an interesting endeavor. I like to shake up my way of doing things as often as possible to look for opportunities for improvement.