Testability & Good Design
Much of the time, the test-driven development yahoogroup is pretty quiet, but it has recently awakened from winter hibernation. The question “Is it OK to add code to a class only to improve its testability?” stirred up a wide-ranging discussion that brought in the topic of what constitutes good design. “Uncle Bob” Martin drew a bold line in the sand with his comment,
One reasonable definition of good design is testability. It is hard to imagine a software system that is both testable and poorly designed. It is also hard to imagine a software system that is well designed but also untestable.
I greatly sympathize with this statement, though I wouldn’t go quite that far. I don’t think it is so hard to imagine code that is testable, but poorly designed. For a trivial counter-case, there could be rampant duplication of testable code. I would call that poorly designed, but it doesn’t affect it’s testability. Therefore I would soften Uncle Bob’s definition to “One reasonable component of the definition of good design is testability.”
To me, the notion of “testable code” is the same thing that “testable circuit” was back when I worked on a custom integrated circuit. Mostly, that depends on the ability to put the circuit or code into a known state, exercise it, and see the interactions with its collaborators and its resulting state.
For ICs, the first level of making a circuit testable was to the internal state predictable and discernible. Sometimes this was accomplished simply, by having the power-up state known, rather than random, and by being able to clock internal nodes into a shift register to be read out on an output pin in a special test mode. That was enough to make it testable, but not generally enough to make it easy to test.
Being able to drive internal nodes to various known states gave a lot more power, allowing “unit testing” of various blocks of circuitry. This generally required some additional hardware added just for testability (a point germane to the original question), but paid handsome dividends in reduced time to test units in production.
With ICs, much of the expense is in the packaging, and that expense was significantly related to the number of pins. Testing equipment evolved to do the equivalent of “the bed of nails” used on circuit boards, but applied to pads on the circuit die that were never bonded out to pins. This allowed easier access to internal nodes, both for driving state and for reading it, prior even to slicing the wafer into individual chips. The heads that probed the circuit had a small nozzle to spray dye on failed circuits so they could be discarded before packaging.
With software, the ability to drive and access internal nodes is much easier–often not requiring any additional logic. Sadly, many programs manage to make these internal nodes inaccessible, in spite of the lack of cost to do otherwise.
So, when I talk about testability, that’s what I mean. And that’s why I quibble slightly with Uncle Bob’s assertion that testable code is necessarily well designed. The converse is pretty easy to believe, but there are cases where, for no apparent reason, the design still sucks.
Well designed code, like well designed circuits, contain the complexity better and are therefore easier to adequately test. This is, to me, an important point. We’re very likely to miss stuff. Let’s make it as hard as possible to miss stuff. And let’s make it as easy as possible to notice when we’ve missed stuff.
In the words of C. A. R. Hoare,
There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies.
Adam Sroka stated his discomfort with calling code testable unless it was, indeed, tested. Again, I’ll be happy with a slightly lower standard. It can be apparent that some code is testable even when not tested. Generally, that code needs to be simple enough that testability can be obvious. In most cases, though, code that isn’t tested is also not clearly testable. To a first order approximation, both Uncle Bob’s and Adam’s statements ring true. I’m not terribly concerned with the absolute truth of either of them. I do, however, prefer Michael Feathers’ assertion that there is a deep synergy between testability and good design.