
Scott Meyers wrote:
David Abrahams wrote:
I'd much rather develop a library of mock objects for testing. Okay a conforming mock stream may be a little work to write, but you write it once and you're done.
I take this to mean that you have not found it necessary/desirable/useful to create separate test and client interfaces. Okay.
My understanding is that the ability to drop in mock objects requires programming to an interface that can be either a "real" object or a mock object, which in turn suggests using either a template parameter or a base class. Suppose, for example, you have this:
class BigHonkinHairyClass { ... // expensive to construct };
and you want to implement some function f that takes a BigHonkinHairyClass as a parameter. The straightforward declaration would be
void f(BigHonkinHairyClass& bhhc); // maybe const, doesn't matter
But now it's hard to drop in a mock. So I assume you'd modify the interface to be either
template<typename T> void f(T& bhhc); // T must more or less model // BigHonkinHairyClass
or this:
class BigHonkinHairyBase { ... // interface to program against -- }; // uses virtuals
class BigHonkinHairyClass: public BigHonkinHairyBase { ... };
class MockHonkinHairyClass: public BigHonkinHairyBase { ... };
void f(BigHonkinHairyBase& bhhc);
Is that correct? In other words, you'd come up with an interface that let clients do what they wanted but that was also mock-friendly for testing purposes? That is, you'd take testability into account when designing your client interface?
This situation should be rare in well-designed code. Either f requires a BigHonkingHairyClass in order to work - by this I mean the precise semantics of BHHC - and it has to be tested using a BHHC; you can't substitute a mock that emulates a BHHC perfectly because it will be a BHHC itself. Or, f doesn't really require a BHHC, and it should be rewritten in one of the two ways above. This allows client A to pass a BHHC and client B to pass something else. From the library design PoV, it doesn't matter whether client B is a test suite or just another module. It is true that in many projects, the lower layers aren't designed as a proper library, since they don't have to serve arbitrary client code. In such a case, having a test suite as a second client can indeed lead to problems like the above. :-) Another angle is that tests should test the behavior that is exercised by the application. If the application uses f with a BHHC, the tests should test f with a BHHC. Testing f with a mock can find some errors, but may easily miss others. This can be substituted by defining a rigorous interface for BHHCs and testing both f and BHHC against that interface, of course, which gets us back to one of the two refactorings given above. (But the f+actual BHHC test should still be part of the suite, IMO.)