Achieving And Recognizing Testable Software Designs is part 1 of a series of articles in which Roy Osherove looks at design for testability. This first part defines “testability”, and then gives a few examples in which Roy refactors C# code to improve its testability. Clear and concise and well worth a read. I’ll be looking out for the next installment.
The standard three- or four-layer models of application architecture seem to dictate the direction of the dependencies between the various “kinds” of object in the system: The UI depends on the application layer, because the UI “drives” what happens “below”. The application layer depends on the business objects, which do all the domain-specific things. The business objects use (and hence depend on) the persistence layer and comms layer, both of which in turn use and depend on external APIs. It is perfectly natural to read a layered model in this way – indeed, that was its purpose.
ui ---> application ---> domain ---> (persistence, comms)
And it is this very interpretation that has made so many applications difficult or impossible to test. Each of those perceived dependencies ends up enshrined in the code structure, and by stealth the design has cast in concrete the layered model. At which point someone like me comes along an exhorts the developers to improve their test coverage. I’m told: “We tried that, but it takes too long to manage the Oracle testdata we’d need to test the GUI,” or something similar. Or the business decides to switch to a new database or enterprise comms architecture, requiring huge swathes of business logic to be re-engineered.
Many of the things we agile proponents take for granted can seem crazy to folks from a traditional background. Take testing for example, and the whole idea that the tests and the design process should be intertwined. Traditional Test/QA departments are organised around the belief that testing is external to the development of the software. From that point of view, the agile community is saying at least two radical things:
- it’s okay – indeed, it’s better – to “re-design” the code so that testing is easier;
- the changes for testability are now part of the system under test, and will be shipped to the user.
Maybe we have to invent new terms for what we do. Because from the above point of view we’re not using the word “tests” in the sense that it has been understood previously…
A couple of years ago I worked with a team that had exactly the problem Michael describes. Many of the classes were nearly impossible to instantiate in a test harness, and many of their methods couldn’t be tested without dire consequences for the development environment. Refactoring in the subsystems that contained these classes was also impossible, because of the unpredictable side-effects incurred by doing almost any method call. In the end, after months of frustration, we decided to very carefully and incrementally throw away those classes and replace them with something testable. In fact, that large-scale refactoring was never finished (due to circumstances outside the team’s control), but the improving code definitely “felt” better.