Channels ▼


The Relationship Between Testability and Good Design

Is there a problem with RuleEvaluator? It turns out that there is. RuleEvaluator has three separate responsibilities: It evaluates, parses, and tokenizes. While code can often exist well for a while in that state, invariably, the separate responsibilities become tangled and the class becomes difficult to maintain. Let's see what happens when we start to fix the design problems.

An improved class
Figure 2: An improved version.

Figure 2 shows the system after we've extracted a class for tokenization. Our testing situation is much better now. The getNextToken method is public on a new class and we can test it easily. Improving the design also fixed the testing problem.

This example might annoy you. You may think that without any need to reuse the code in getNextToken() or hasMoreTokens(), pulling them out into their own class is pointless — it increases the number of classes needlessly. But making this move does make the code more modular. We could reuse RuleTokenizer at some point in the future. At the very least, it is now very clear where we have to go in the code to make changes in tokenization functionality. Our code is more orthogonal and it is easier to change one thing without impacting another.

If this were the only example of lack of testability being an indicator of poor design, it could just be considered a fluke, but it isn't. In general, pain in unit testing is an indication that there is something wrong. Sometimes this pain is very obvious. For example, if you have to create too many objects to get the one you want to test, it is often an indication of excessive coupling. Other cases are subtler. Let's examine a few.

Other Indicative Smells

It's generally accepted that unit tests should run in isolation. We should be able to execute each of them without affecting the state of any other test. When we don't work this way, we end up with unexplained failures that are hard to reproduce. It often takes a considerable amount of debugging to find the source of these problems. The design problem that this indicates is global mutable state. We've known for decades that global variables can be problematic. Unit testing makes the pain evident and, once again, if we work on the design problem, unit testing becomes easier.

The fact that each of our tests should execute as if they were in a hermetically sealed container often helps us see other design issues. Often, I notice that when teams try to make key classes of their system independently testable, they discover that various objects don't release resources when they are destroyed. In the application, this sort of thing may never be noticed. When you are running thousands of unit tests, however, resource leaks become evident very quickly.

One of the most pervasive testing challenges points toward design issues as well. Teams often notice that unit testing in the presence of a third-party API is difficult. A classic example is UI-intensive code. Developers often place computational code in event handlers. It would be nice to be able to test those computational pieces independently, but it is nearly impossible because you can only exercise the code by triggering events from the UI. The design problem is lack of separation of concerns. Again, once the computation concern is separated from the UI concern, the computation is easily tested.

The big question at the end of all of this is, "Why?" Why do testing problems indicate design problems? A few years ago, I read an interesting paper by Thomas Mullen. In it, he hypothesized that many of the generally accepted principles of good design are "good" because they mirror our cognitive processes. It is easier for us to understand things in small chunks, and it helps when those chunks are independent. Perhaps the reason why unit testing serves as a good probe of design is because it is, essentially, a cognitive process. When we write tests, we are going through a reasoning process, and we are making our reasoning explicit in test code. Pieces of a program that are hard to understand with tests are likely to be hard to understand without them.

It's an odd situation to be in, isn't it? Pain isn't fun. We can create tooling that eliminates much of the pain of unit testing, but then what would we have? We might just end up with less of the feedback that could trigger us to discover better design.

Michael Feathers is the founder and Director of R7K Research & Conveyance, a company specializing in software and organization design. He is also the author of Working Effectively with Legacy Code.

Related Reading

More Insights

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.