EDIT 12/23/2008: I believe this post has been massively misconstrued in the comments. Apparently, my analogy here was terrible from the way so many people are distorting what I was trying to say. For the record,
I DID NOT SAY DON’T WRITE TESTS
I DID NOT SAY YOU MUST BE A MASTER OF ALL DESIGN LORE BEFORE YOU GET TO WRITE A TEST
I DID NOT SAY THAT YOU MUST HAVE A PERFECT, PURIST DESIGN FIRST
I DID NOT SAY DO NOT WRITE TESTS FOR LEGACY CODE
I said, or attempted to say, that testing and testability cannot be decoupled from design, and any effort towards automated testing / unit testing / TDD is probably not going to be successful until the team has a good understanding of the basics ( and I did say basics here) of fundamental software design.
My most recent Patterns in Practice article entitled Design for Testability is available in this month’s issue of MSDN Magazine. This time around, I tried to tackle some of the design issues around supporting testability. I’ve written four articles so far for MSDN on design fundamentals. If you look at the whole body of work, there’s a very common theme: good, fundamental design is being able to do one thing at a time. I want to decompose the system so that I can change one thing at a time with less risk of destabilizing other parts of the system. I want to be able to understand one element of the system at a time. I want to solve one little problem at a time instead of tackling the whole mess in one try.
Testability as a design concept is right in line with this kind of thinking. Testability means being able to easily create rapid, effective, and focused feedback cycles against your code with automated tests. Testability isn’t some strange and unnatural new way to shape code. For the most part (ok, it might be if you cut your programming teeth with Microsoft’s RAD tools), Testability goes hand in hand with the same structural qualities like cohesion and coupling that we use to create code that’s easier to write, change, and understand. On the other hand, we can use Testability within design techniques like Test Driven Development and/or Behavioral Driven Development to inform and help guide our design decisions.
Unit Testing – Solid Design == PAIN!
A couple months back Roy Osherove wrote a post entitled Unit Testing decoupled from Design == Adoption that posited the theory that things will work out better for the larger community if we drop all this hard design theory and TDD discipline and just get developers to write unit tests first. The theory being that more developers would be more likely to adopt some sort of unit testing, fail, then naturally circle back to learning about design or try out TDD after they get started.
I couldn’t possibly disagree more with Roy on this. I would describe Roy’s suggestion as akin to telling an out of shape 30 something with no prior athletic experience to start playing competitive basketball by just walking onto the court and getting into a pickup game. That out of shape 30 something fellow isn’t going to be able to compete, and he’s likely to limp off too discouraged and humiliated to continue. After all, why would the average person sign up for another beating? Likewise, a developer with little foundational knowledge about good design principles will be very unlikely to succeed in any way with unit testing because it’ll be just too hard with poorly structured code (and no, I don’t think any “Isolation” framework will change that). I would tell the 30 something to work on his cardiovascular conditioning (and his jumpshot) before he steps onto that basketball court for the first time. I’d tell that developer to focus on his understanding (not rote knowledge, but understanding) of good design first and be ready to succeed upfront.
I guess to summarize and beat the analogy into the ground, cardiovascular health is beneficial for more than just playing pickup basketball after work and good design is valuable for more things than making unit testing and acceptance testing easier. In short, good design is far more beneficial and important than the practice of unit testing. However, good design + TDD/Behavioral Driven Development/Acceptance Test Driven Development? That’s where the real rewards happen. Don’t settle for mediocre efforts. Do both and reap the rewards.
A couple years ago I was reading some writing from older guys like Steve McConnell and Robert Glass that stated that Test Driven Development couldn’t work because unit testing was proven to be only marginal cost effective – and they based those statements on studies that dated back to COBOL developers in the ‘70s and ‘80s writing purely procedural code. TDD with well factored OOP or Functional code compared to unit testing tightly coupled structured programming is apples and oranges. I have consistently found that the cost effectiveness of unit testing and Test Driven Development is almost entirely based on the structural design of the class structure of the system being built.
I think this is a huge topic, and one measly 3500 word article isn’t going to cover the topic adequately. Learning xUnit tools and even mock object frameworks is a piece of cake compared to learning how to design and write code that enables efficient testing. Here are some of my older writings from the time when I had just gotten over the hump and started to figure out how to compose code to enable Test Driven Development.
The next article is already in editing. For February I’m discussing Ruby on Rails inspired design ideas like convention over configuration, opinionated software, sensible defaults, and once and only once. For April I’m going to write about data access and persistence patterns to fit in with the theme of that month’s issue. After that?
- Patterns for Testability – I missed a lot of important topics in this month’s article. I’d like to hit separated presentation patterns and Inversion of Control as a design pattern maybe.
- Designing an Internal DSL
- Some kind of post on Composition and IoC
- Patterns for Client/Server Communication
Any requests? Thoughts? I’m bypassing (D)DDD for the most part because there are articles on the way on those very topics from more qualified folks (Don’t tell anyone, but I’m not entirely sure I completely buy off on DDD).
The first three articles in this series are effectively tributes to my software design heroes (Fowler, Rebecca Wirfs-Brock, Uncle Bob Martin, and the Pragmatic Programmers). This last one was largely based on my own prior writings on testability and design. It’s also the one I’m least happy with. Back to hero tributes after this;)