Unit Testing C++ Templates and Mock Injection Utilizing Traits

[ad_1]

Unit testing your template code comes up infrequently. (You take a look at your templates, proper?) Some templates are straightforward to check. No others. Generally it is not clear how one can about injecting mock code into the template code that is below take a look at. I’ve seen a number of explanation why code injection turns into difficult.

Right here I’ve outlined some examples under with roughly rising code injection issue.

  1. Template accepts a sort argument and an object of the identical sort by reference in constructor
  2. Template accepts a sort argument. Makes a replica of the constructor argument or just doesn’t take one
  3. Template accepts a sort argument and instantiates a number of interrelated templates with out digital capabilities

Lets begin with the straightforward ones.

Template accepts a sort argument and an object of the identical sort by reference in constructor

This one seems straight-forward as a result of the unit take a look at merely instantiates the template below take a look at with a mock sort. Some assertion is perhaps examined within the mock class. And that is about it.

After all, testing with only a single sort argument says nothing about remainder of the infinite variety of varieties that one might go to the template. A flowery technique to say the identical factor is templates are universally quantified so we would need to get little extra intelligent for extra scientific testing. Extra on that later.

For instance,

template <class T>
class TemplateUnderTest {
  T *t_;
public:
  TemplateUnderTest(T *t) : t_(t) {}

  void SomeMethod() {
    t->DoSomething();
    t->DoSomeOtherThing();
  }
};

struct MockT {
  void DoSomething() { 
    // Some assertions right here. 
  }
  void DoSomeOtherThing() { 
    // Some extra assertions right here. 
  }
};

class UnitTest {
  void Test1() {
    MockT mock;
    TemplateUnderTest<MockT> take a look at(&mock);
    take a look at.SomeMethod();
    assert(DoSomethingWasCalled(mock));
    assert(DoSomeOtherThingWasCalled(mock));
  }
};

Template accepts a sort argument. Makes a replica of the constructor argument or just doesn’t take one

On this case accessing the article contained in the template is perhaps inaccessible as a result of entry privileges. buddy courses may very well be used.

template <class T>
class TemplateUnderTest {
  T t_;
  buddy class UnitTest;
public:
  void SomeMethod() {
    t.DoSomething();
    t.DoSomeOtherThing();
  }
};
class UnitTest {
  void Test2() {
    TemplateUnderTest<MockT> take a look at;
    take a look at.SomeMethod();
    assert(DoSomethingWasCalled(take a look at.t_)); // entry guts
    assert(DoSomeOtherThingWasCalled(take a look at.t_)); // entry guts
  }
};

The UnitTest::Test2 can merely attain into the center of TemplateUnderTest and confirm the assertions on the interior copy of MockT.

Template accepts a sort argument and instantiates a number of interrelated templates with out digital capabilities

For this case, I am going to take a real-life instance: Asynchronous Google RPC

In C++ async gRPC, there’s one thing known as CallData, which because the title suggests, shops the information associated to an RPC name. A CallData template can deal with a number of RPC of various varieties. So it is not unusual to make it a template.

A generic CallData accepts two sort arguments Request and Response. That is the way it might seem like

template <class Request, class Response>
class CallData {
  grpc::ServerCompletionQueue *cq_;
  grpc::ServerContext context_;
  grpc::ServerAsyncResponseWriter<Response> responder_;
  // ... some extra state
public:
  utilizing RequestType = Request;
  utilizing ResponseType = Response;

  CallData(grpc::ServerCompletionQueue *q)
    : cq_(q),
      responder_(&context_) 
  {}
  void HandleRequest(Request *req); // application-specific code
  Response *GetResponse(); // application-specific code
};

The unit take a look at for CallData template should confirm the habits of HandleRequest and HandleResponse. These capabilities name plenty of capabilities of the members. So ensuring they’re known as in accurately is paramount to the correctness of CallData. Nevertheless, there is a catch.

  1. Some varieties from grpc namespace are instantiated internally and never handed by way of the constructor. ServerAsyncResponseWriter and ServerContext, for instance.
  2. grpc::ServerCompletionQueue is handed as an argument to the constructor but it surely has no digital capabilities. Solely digital destructor.
  3. grpc::ServerContext is created internally and has no digital capabilities

The query is how one can take a look at CallData with out utilizing full-blown gRPC within the checks? mock ServerCompletionQueue? mock ServerAsyncResponseWriter, which is itself a template? and on and on…

With out digital capabilities, substituting customized habits turns into difficult. Hardcoded varieties corresponding to grpc::ServerAsyncResponseWriter are unimaginable to mock as a result of, effectively, they’re hardcoded and never injected.
/>
It makes little sense to begin passing them as constructor arguments. Even when do this, it could be meaningless as a result of they might be closing courses or just don’t have any digital capabilities.

So, what provides?

As a substitute of injecting customized habits by inheriting from a typical sort (as achieved in Object-Oriented programming), INJECT THE TYPE ITSELF. We use traits for that. We specialize the traits in another way relying upon whether or not it is manufacturing code or unit take a look at code.

Think about the next CallDataTraits

template <class CallData>
class CallDataTraits {
  utilizing ServerCompletionQueue = grpc::ServerCompletionQueue;
  utilizing ServerContext = grpc::ServerContext;
  utilizing ServerAsyncResponseWriter = grpc::ServerAsyncResponseWrite<typename CallData::ResponseType>;
};

That is the first template for the trait and used for “manufacturing” code. Let’s use it within the CallData template.

/// Unit testable CallData
template <class Request, class Response>
class CallData { 
  typename CallDataTraits<CallData>::ServerCompletionQueue *cq_;
  typename CallDataTraits<CallData>::ServerContext context_;
  typename CallDataTraits<CallData>::ServerAsyncResponseWriter responder_;
  // ... some extra state
public:
  utilizing RequestType = Request;
  utilizing ResponseType = Response;

  CallData(typename CallDataTraits::ServerCompletionQueue *q)
    : cq_(q),
      responder_(&context_) 
  {}
  void HandleRequest(Request *req); // application-specific code
  Response *GetResponse(); // application-specific code
};

Given the above code, it is clear that manufacturing code remains to be utilizing the kinds from the grpc namespace. Nevertheless, we will simply substitute the grpc varieties with mock varieties. Checkout under.

/// In unit take a look at code
struct TestRequest{};
struct TestResponse{};
struct MockServerCompletionQueue{};
struct MockServerContext{};
struct MockServerAsyncResponseWriter{};

/// We wish to unit take a look at this kind.
utilizing CallDataUnderTest = CallData<TestRequest, TestResponse>;

/// A specialization of CallDataTraits for unit testing functions solely.
template <>
class CallDataTraits<CallDataUnderTest> {
  utilizing ServerCompletionQueue = MockServerCompletionQueue;
  utilizing ServerContext = MockServerContext;
  utilizing ServerAsyncResponseWriter = MockServerAsyncResponseWrite;
};

MockServerCompletionQueue mock_queue;
CallDataUnderTest cdut(&mock_queue); // Now injected with mock varieties.

Traits allowed us to decide on the kinds injected in CallData relying upon the scenario. This method has zero efficiency overhead as no pointless digital capabilities had been created to inject performance. The approach can be utilized with closing courses as effectively.

[ad_2]

Leave a Comment

Your email address will not be published. Required fields are marked *