-
Notifications
You must be signed in to change notification settings - Fork 8
Template classes for gmock
Note: We have gone the route of using abstract classes to define interfaces which are then (1) mocked and (2) implemented. Driver code contains base pointers to the implementation. The part of this page that deals with templates presents an alternative which we will revisit in the future.
Let X
be a class in our code that calls functions from another class that we'd like to mock, Y
. So, our tests will call member functions of X
and check that certain functions of Y
were called properly (in correct order, with correct arguments, ...).
To mock Y
, gmock needs to implement its own implementation of the functions being called. But Y
already has real implementations being used in production, and we would like to keep the implementation of X
the same (i.e. don't change the code to use different function calls). In the production situation, the real functions of Y
will need to be called by X
, and in the testing situation, the mocked functions of Y
will need to be called by X
. How can we mock Y
without changing the implementation of X
, while hiding all the mocking dependencies from X
?
We can prepare the classes for mocking with gmock in 2 ways:
-
Abstract class with virtual functions: Define an abstract class to use as a common interface between the mock and production class, have
X
call functions from the abstract class only, and haveY
and a mocked class ofY
inherit from it. -
Templated class: Use templating to parameterize
X
, and instantiate objects ofX
with either the production classY
or mock class.
In our case, X == PcInterface
.
How to use this method is described here https://github.com/google/googletest/blob/master/googlemock/docs/ForDummies.md#a-case-for-mock-turtles.
In our case (at this point in time https://github.com/utra-robosoccer/soccer-embedded/commit/6a249f75f9f8f24ac7de4a5b05bdf793326d5bf2), PcInterface
would call a common abstract interface UdpInterface
which then directs function calls to UdpInterface
to a sub class LwipUdpInterface
or MockUdpInterface
- depending on what PcInterface
was initialized with. PcInterface
contained a pointer UdpInterface*
, and this pointer would be set to LwipUdpInterface
in a production situation, and MockUdpInterface
in a testing situation.
As an example, to initialize the class and call PcInterface::setup()
(as an example) one would do the following: (also see code at https://github.com/utra-robosoccer/soccer-embedded/commit/6a249f75f9f8f24ac7de4a5b05bdf793326d5bf2)
Testing
MockUdpInterface udpInterface;
PcInterface testPcInterfaceObject;
testPcInterfaceObject.setUdpInterface(udpInterface);
EXPECT_CALL(udpInterface, udpNew()).times(1)
testPcInterfaceObject.setup();
Production
LwipUdpInterface udpInterface;
PcInterface testPcInterfaceObject;
testPcInterfaceObject.setUdpInterface(udpInterface);
testPcInterfaceObject.setup();
In both cases, the implementation of PcInterface::setup()
(as an example) contains a call like:
udpInterface->udpNew()
And the udpInterface
member of PcInterface
looks like:
UdpInterface *udpInterface;
Note: if the udpInterface
member were instead declared as LwipUdpInterface *udpInterface
, vtables would not be used as LwipUdpInterface
is not an abstract class, so it is known at compile time that a function call to a LwipUdpInterface
method will be made.
The decision on whether to make a function call to LwipUdpInterface
or MockUdpInterface
is made at run time. A function call to the abstract class UdpInterface
is known at compile time, because udpInterface
is of type UdpInterface*
- but whether the call is LwipUdpInterface.udpNew()
or MockUdpInterface.udpNew()
cannot be decided. C++
decides at runtime using vtables, existing in memory, which associate an abstract class pointer with an instance of a child class. When the function call udpInterface->UdpNew()
is made, C++
accesses the vtable in memory, and finds a pointer to the function call in either LwipUdpInterface
or MockUdpInterface
, depending on what object udpInterface
points to.
Opinionated (not wiki material):
The problem with making the decision on what function call was made at runtime is that an extra memory access is required, to access the vtable. Although this operation is not too expensive (about the same cost as accessing a statically allocated array), there is no need for the decision to be deferred to runtime (e.g. there is never a situation where the udpInterface
pointer would be assigned to something else - it is always either production or testing and stays that way during runtime). The decision on whether a testing function call or a production function call has been made should be specified at compile time, if possible/practical. We also may as well avoid the extra memory lookup whenever function calls to UdpInterface
are made.
gmock suggests another way, to mock functions that do have an existing implementation (i.e. not functions of an abstract class): https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#mocking-nonvirtual-methods. Instead of defining an abstract interface UdpInterface
through which all mocked function calls are made, the class that makes the function calls PcInterface
is parameterized using templates to specify either the mocked class or production class when an object of PcInterface
is instantiated.
Our current code (see https://github.com/utra-robosoccer/soccer-embedded/commit/9daf73eb160925de32964e42224368cc2e8201ca) has a class LwipUdpInterface
which implements the production functionality of UDP communication, and MockUdpInterface
which has methods implemented by gmock, for mocking (see PcInterface_test.cpp
). PcInterface
is a template, declared as template <class UdpInterface> PcInterface
. The difference here is that if e.g. PcInterface<MockUdpInterface>
appears in the code, the compiler will generate an actual class PcInterface
that contains MockUdpInterface
wherever UdpInterface
appears in the template. This is opposed to maintaining a vtable in memory pointing to the child class instance and accessing the vtable each function call.
To initialize the class and call PcInterface::setup()
(as an example) one would do the following: (also see https://github.com/utra-robosoccer/soccer-embedded/commit/9daf73eb160925de32964e42224368cc2e8201ca)
Testing
MockUdpInterface udpInterface;
PcInterface testPcInterfaceObject<MockUdpInterface>;
EXPECT_CALL(udpInterface, udpNew()).times(1)
testPcInterfaceObject.setup();
Production
LwipUdpInterface udpInterface;
PcInterface testPcInterfaceObject<LwipUdpInterface>;
testPcInterfaceObject.setup();
In both cases, the implementation of PcInterface::setup()
(as an example) contains a call like:
udpInterface->udpNew()
The udpInterface
member of PcInterface
is different in each case (generated by the compiler):
Testing
MockUdpInterface *udpInterface;
Production
LwipUdpInterface *udpInterface;
Instantiating the template defines a class PcInterface
with definite type - depending on what was passed into <>
.
Opinionated
Due to the way the linker works, the template class PcInterface
needs to be in the same compilation unit - usually this means including them in the same header file. The issue with this is that usually we have function definitions exist in the .cpp
file; this does not follow our usual way of ordering the code. An acceptable solution I think is to have the definitions in the .h
file for any templated classes, separated from the class definition, and create the corresponding PcInterface.cpp
file with a note referring to the .h
file. Alternatives are listed: https://code.i-harness.com/en/q/858ec5 (see https://github.com/utra-robosoccer/soccer-embedded/issues/71#issuecomment-421701996).