Design For TestingSource: comp.objectDate: 21-Oct-99
Problem: How can you test code that needs another component to run? And why does making classes easy to test also improves the design?
John Burton presented the problem: Many of my classes involve communication across a network to a server that probably isn't going to available to run random tests on, or involve looking up items in databases which don't necessarily exist during development... Do you suggest that I create dummy network interfaces and perhaps my own local databases just for unit testing purposes? I'd presumably have to modify the code to work with my own versions and not the "real" ones? Does this invalidate the testing? Should I modify the code to support running either with the real database or for testing? That's adding functionality that is really only required for testing the class and not for any real requirements.
Michael C. Feathers replied: I think that what you really need is a strong abstraction layer between your database and/or network and the rest of your application. [...] This is why the Dependency Inversion Principle (DIP) is so important. If your code obeys DIP, you can substitute in testing impostors easily. One thing that I find over and over again is that the act of unit testing code is a critical test of its structure as well as its functionality. With good separation of concerns, it is not as hard to test pieces of a system independent of database and network mechanisms. [...] Abstract interfaces really facilitate testing. But, they also do much more. They reduce the dependency in systems, making them easier to change.
Robert C. Martin explained the Dependency Inversion Principle: DIP is a dependency management principle. It recommends the use of abstraction [...] to invert annoying dependencies. For example: Lets say class A calls method f on class B. Lets also say that A lives in package X and B lives in package Y. Therefore A depends upon B, and X depends upon Y. Now, let us further stipulate that the direction of the dependency between X and Y is problematic. We can invert that dependency by employing abstraction. Within X we can create an abstract class called AService. Within AService we'll define the abstract method f. B will inherit (or implement) AService. Thus, instead of A depending upon B, A now depends upon AService and B depends upon AService. The dependency from X to Y has been inverted. Y now depends upon X. Notice, there is no polymorphism involved; though there is now the opportunity for polymorphism to be added. The DIP states that dependencies upon concretions are problematic. That we want all of our dependencies to be upon abstractions. This means many things. All #includes in a C++ program should reference the header files of abstract classes. A derived class that overrides a function, should override a pure function. A class that calls a method should call a pure method. A function that takes an argument should take a pointer to an abstract class, or a template argument, etc, etc, etc. DIP says "Depend upon abstractions" [...]. More Info: Robert C. Martin, The Dependency Inversion Principle WikiWikiWeb, Unit Testing Is Design WikiWikiWeb, Code Unit Test First WikiWikiWeb, Test Driven Programming WikiWikiWeb, Un-Unit-Testable Units
|