Suppose I have classes
class Inner {
public:
void doSomething();
};
class Outer {
public:
Outer(Inner *inner); // Dependency injection.
void callInner();
};
Proper unit-testing says I should have tests for Inner
. Then, I should have tests for Outer
that uses not a real Inner
but rather a MockInner
so that I would be performing unit-tests on the functionality added by just Outer
instead of the full stack Outer
/Inner
.
To do so, Googletest seems to suggest turning Inner
into a pure abstract class (interface) like this:
// Introduced merely for the sake of unit-testing.
struct InnerInterface {
void doSomething() = 0;
};
// Used in production.
class Inner : public InnerInterface {
public:
/* override */ void doSomething();
};
// Used in unit-tests.
class MockInner : public InnerInterface {
public:
/* override */ void doSomething();
};
class Outer {
public:
Outer(Inner *inner); // Dependency injection.
void callInner();
};
So, in production code, I would use Outer(new Inner)
; while in test, Outer(new MockInner)
.
OK. Seems nice in theory, but when I started using this idea throughout the code, I find myself creating a pure abstract class for every freaking class. It's a lot of boiler-plate typing, even if you can ignore the slight run-time performance degradable due to the unnecessary virtual dispatch.
An alternative approach is to use templates as in the following:
class Inner {
public:
void doSomething();
};
class MockInner {
public:
void doSomething();
};
template<class I>
class Outer {
public:
Outer(I *inner);
void callInner();
};
// In production, use
Outer<Inner> obj;
// In test, use
Outer<MockInner> test_obj;
This avoids the boiler-plating and the unnecessary virtual dispatch; but now my entire codebase is in the freaking header files, which makes it impossible to hide source implementations (not to mention dealing with frustrating template compilation errors and the long build time).
Are those two methods, virtuals and templates, the only ways to do proper unit-testing? Are there better ways to do proper unit-testing?
By proper unit-testing, I mean each unit-test tests only the functionalities introduced by that unit but not the unit's dependencies also.
A Mock object is one kind of a Test Double. You are using mockobjects to test and verify the protocol/interaction of the class under test with other classes. Typically you will kind of 'program' or 'record' expectations : method calls you expect your class to do to an underlying object.
Using mock objects allows developers to focus their tests on the behavior of the system under test without worrying about its dependencies. For example, testing a complex algorithm based on multiple objects being in particular states can be clearly expressed using mock objects in place of real objects.
Only use a mock (or test double) “when testing things that cross the dependency inversion boundaries of the system” (per Bob Martin). If I truly need a test double, I go to the highest level in the class hierarchy diagram above that will get the job done. In other words, don't use a mock if a spy will do.
A mock object can be useful in place of a real object that: Runs slowly or inefficiently in practical situations. Occurs rarely and is difficult to produce artificially. Produces non-deterministic results.
I don't think you must mock out every dependency of your tested class in practice. If it is complicated to create, use or sense through, then yes. Also if it directly depends on some unneeded external resource such as a DB, network or filesystem.
But if none of these is an issue, IMO it is OK to just use an instance of it directly. As you already unit tested it, you can be reasonably sure that it works as expected and doesn't interfere with higher-level unit tests.
I personally prefer working unit tests and simple, clean, maintainable design over adhering to some ideal set up by unit test purists.
each unit-test tests only the functionalities introduced by that unit but not the unit's dependencies also.
Using a functionality and testing a functionality are two very different things.
I also think that using an instance on Inner directly is OK. My problem is mocking external objects that are not part of my code (provided through static libraries or DLLs, sometimes 3rd party). I am inclined to rewrite a mock DLL or library with the same class names, and then linking differently for test. Modifying the header file of the external dependency to add "virtual"s seems unacceptable to me. Does anyone have a better solution?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With