Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking and dependency injection in C++

I'm struggling to do unit testing with googlemock and dependency injection in C++. Mocks and dependency injection significantly ease code testing, but they heavily rely on virtual methods. While classes in other languages use virtual methods by default, it is not the case of C++. I'm using C++ to create a low-overhead performance measuring framework, so just making every single class inherit from an interface (w/ pure virtual methods) is not a desirable option.

Specifically, I'm having issues with testing classes that contains collections of objects such as the following one:

struct event_info { /* ... */ };

template<typename Event>
class event_manager {
public:
  event_manager(const std::vector<event_info>& events) {
    std::transform(begin(events), end(events),
        std::back_inserter(events_),
        [](const event_info& info) { return Event{info}; });
  }

  void read() {
    for (auto& e : events_)
      e.read();
  }

  // ...

private:
  std::vector<Event> events_;
  // ...
};

To test this class I could do the following:

class mock_event {
public:
  MOCK_METHOD0(read, void());
};

TEST(event_manager, test) {
  event_manager<mock_event> manager;
  // ...
}

But this won't work as I cannot set the expectations for the mock object, and mock objects from googlemock are not copyable (therefore, the call to std::transform fails to compile).

To solve this issue, when testing I could use pointers instead (e.g., event_manager<mock_event*>) and pass a factory to the event_manager constructor. But, this won't compile because of calls such as e.read() (it should be e->read() instead when testing).

I could then use type traits to create a method that if given a reference just returned the reference, and if given a pointer, then dereference the pointer (e.g., dereference(e).read()). But, this just keeps adding tons of complexity and it doesn't look like a good solution (especially if needs to be done to test all classes that contain a collection of objects).

So, I was wondering whether there is a better solution to this, or it is just that mocking and dependency injection are not very suitable techniques for C++.

like image 998
betabandido Avatar asked Nov 09 '22 07:11

betabandido


1 Answers

I assume that you made appropriate performance measurements with a simple class emulating your standard use case before deciding that your application cannot tolerate going through the overhead of a pointer dereference to perform a virtual function call.

If you read the gmock documentation, they have a "high performance mocking" section where they show how to use templates in production code to allow to mock non virtual functions.

I think that the first rule of code (production or test) is to keep the code as simple as possible, and so I am not convinced of changing production code with templates just to be able to test it (although on the other hand I am completely favorable to use TDD as a critique and guide to the design of my production code).

As such, it looks like that your application needs another mocking framework, one that allows to perform mocking at link-time as opposed to run-time.

Check out cpputest and cppumock (https://cpputest.github.io/), that is able to mock both C free-standing functions and C++ non virtual methods.

The price to pay with cpputest/cppumock is that it requires more boilerplate than gmock, but it is very good.

like image 200
marco.m Avatar answered Nov 14 '22 23:11

marco.m