Skip to content
This repository has been archived by the owner on Sep 27, 2021. It is now read-only.

Template classes for gmock

Tyler Gamvrelis edited this page Nov 17, 2018 · 9 revisions

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.

Goal

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, ...).

Problem

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?

Solution

We can prepare the classes for mocking with gmock in 2 ways:

  1. 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 have Y and a mocked class of Y inherit from it.
  2. Templated class: Use templating to parameterize X, and instantiate objects of X with either the production class Y or mock class.

In our case, X == PcInterface.

Method 1 - abstract class with virtual functions

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.

Initialization - abstract class with virtual functions

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.

Method 2 - templated class

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.

Initialization - templated class

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).