4

In Appendix A to The Art of Unit Testing, Roy Osherove, speaking about ways to write testable code from the start, says,

An abstract class shouldn't call concrete classes, and concerete classes shouldn't call concrete classes either, unless they're data objects (objects holding data, with no behavior). (259)

The first half of the sentence is simply Dependency Inversion from SOLID. The second half seems rather extreme to me. That means that every time I'm going to write a class that isn't a simple data structure, which is most classes, I should write an interface or abstract class first, right? Is it really worthwhile to go that far in defining abstract classes an interfaces? Can anyone explain why in more detail, or refute it in spite of its benefit for testability?

3
  • 1
    Perhaps when he wrote his book, a lot fewer frameworks allowed you to mock concrete classes :) Sep 14, 2012 at 18:02
  • @dasblinkenlight Good thought, but he actually discusses tons of mock frameworks in the book, including one that he himself worked on. He does mention later that those frameworks can be seen to reduce the need to design for testability.
    – Kazark
    Sep 14, 2012 at 18:04
  • 2
    Empirical rule #1 of software engineering (IMO): Your software will expand, but not in the direction you anticipated. So strike first, by anticipating everything (and having abstract foundations).
    – K.Steff
    Sep 16, 2012 at 3:22

5 Answers 5

11

That means that every time I'm going to write a class that isn't a simple data structure, which is most classes, I should write an interface or abstract class first, right?

Yup.

Is it really worthwhile to go that far in defining abstract classes an interfaces?

No, but you should err toward more interfaces than less.

Can anyone explain why in more detail, or refute it in spite of its benefit for testability?

I would argue that it is fine to use concrete classes directly as long as those concrete classes are not part of the interface that the consumer is implementing/providing. As soon as they're part of the public interface rather than the implementation of a module, they either need to be abstracted or they need to be stable enough that you're sure that code isn't going to need to change or be polymorphic.

4

I don't think he means you should write an abstract class for each class, necessarily. I think he means there should be a way to substitute a test class for the concrete class. This might be done via a DI container, for example.

It's usually worthwhile to go that far because you are usually going to actually write a test that substitutes a test class for the concrete class, but if you don't need the test then you don't need to bother.

3

IMHO a more pragmatic approach is not to think in "classes" when decoupling parts of your system for better testability, but to think in "components" - bigger functional units with a well-defined task which may be composed of one or more classes. If one component needs the service of another one, then it should not use it directly, but only via an interface. How this interface looks like can be very component dependent, it may be utilize the language definition of an interface in your programming language, it may be just a function or call-back, it may use streams or some kind of "data objects".

To my experience, it is also a good idea for testability to design your component interfaces in a way you can provide all input and output in-memory. For example, say you have component A which uses objects of your database as input and output, and component B, which relies on the output of A, and utilizes the database, too. Then provide a way that A produces the data objects just in-memory, and a way that B can accept those objects from A in-memory too, without any interaction with a database. That makes writing automatic tests for A and B a hell lot of easier (and the tests running much faster).

2

Its only worth going to those extremes if you drink the unit testing/TDD Kool-Aid. To make unit testing easier to accomplish certain designs have to be followed. Some of these designs go against traditional OO design concepts. It is up to you to decide whether the potential gains from unit tests out weigh the cost of having to make some poor design considerations. Unit tests are far from the silver bullet many try to make them out to be, but it is the current design fad.

5
  • 1
    Many people me included believe that the "poor design considerations" you're referring to are actually good designs that are one of the benefits of TDD, it forces an amount of decoupling which you might not otherwise go after. I'm not saying you're wrong here, just pointing out how subjective you're being by sharing the opposite subjective viewpoint. Sep 14, 2012 at 18:28
  • 5
    How is separating interface from implementation going against traditional OO design concepts again?
    – Telastyn
    Sep 14, 2012 at 18:46
  • 2
    Please could you elaborate on which traditional OO design concepts you're talking about here? The ones which I'm aware of are generally about Interface Segregation, Single Responsibility, Encapsulation, Reusability, High Cohesion and Loose Coupling. Every book/article i've ever read on BDD and TDD strongly advocates all of those things, so it must be something else. Sep 14, 2012 at 19:11
  • No, the question is how does a concrete class calling another concrete class violate OO design principles?
    – MebAlone
    Sep 15, 2012 at 5:04
  • 1
    Surely this is good OO practice. Abstraction?
    – jamesj
    Sep 15, 2012 at 9:10
0

The Gang of Four also imply that an abstract class or interface should be defined for every concrete class. Right after introducing the principle Program to an interface, not an implementation—and thus, as a direct consequence of it—they say (18):

Don't declare variables to be instance of particular concrete classes.

This is not possible unless for every concrete class that is instantiated, an abstract class or interface exists.

Thus, it would appear that this is a case where designing for testability promotes what was already considered good object-oriented design. Given that Design Patterns came out almost two decades ago, I would say that the concurrence of the Gang of Four with Osherove is a sign that all that unit testing hasn't made him sick in the head. Rather, he is simply promoting good design.

Design Patterns is one of the most highly respected books in object-oriented design, even now. So though, like with other programming principles, there is no reason to adhere to Osherove's advice with a legalism that looses site of other aspects of good design such as KISS, his advice obviously stands in a tradition of well-respected thought on object-oriented design.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.