Testability
In my prior post, I might have given you the impression that it is necessary to test the code as given. That's the wrong impression. In my view, one of the major quality factors of any design is how easy it is to test it, to maintain it, and ultimately to evolve it. Usually, the metrics you collect on the maintenance and enhancement efforts are trailing indicators, at least with respect to the quality of the design. Writing unit tests is the primary way by which one discovers whether the design is easy to use. If it is hard to test, it is probably hard to use, hard to maintain, and hard to evolve. All of that suggests that you might want to refactor some of the design now before it escapes in the wild.
Let's do a quick review of the costs associated with testing and provide some bad examples for each one of them:
- First, there is the cost of setting up a context for the test. If there are a lot of situations where the the test code must set up dozens of different objects before a given test can be run, that's an indication that there's a problem. The design is fragile; changes in any of those objects will very likely cause ripple effects through many other objects connected with that same test.
- Second, getting the actual results of the test implication. If one result of invoking the the logic under test is a change in state that cannot be explicitly examined but only inferred by subsequent calls to the code under test, that's also a problem that should be addressed in restructuring of the design.
- Third, defining the expected results. If it is necessary to look in dozens of places for various properties and values to determine if the results are correct, that adds to the difficulty of performing the test.
- Fourth, comparing the expected results to the actual results. While less of a problem than the cost of the context for the test, if the results contained dates or time stamps or unique identifiers that can change from test to test, this makes it very difficult to determine whether the results are as expected or not.
- Fifth, diagnosing the problem detected by a failure in a test. If a given test involves dozens of different classes, the failure could be any place within those classes. That means it to search for quite awhile before you detect the cause of the failure.
It is hard to write tests, it's probably also hard to use the functionality that's embodied within the design. If it is hard to write the test, it's probably also hard to make any changes to the design; things are tightly coupled to each other resulting in significant "Ripple" effects.
This is one of the reasons why I am so enamored of test driven design; it provides an early warning of overly complicated designs.
Jon Stonecash is a technology consultant and has been designing, developing, and testing various kinds of software for such a long time that he has had the opportunity to make most of the serious software development mistakes at least once. His long term interests center about databases and the aspects of the application that handle data access and business logic. He is also interested in the tools that assist the development process, particularly code generation.