C++ High performance unit testing with Google Mock?
Asked Answered
L

2

6

I'm using Google Mock, and I'm struggling to mock out C++ system calls (specifically the C++11 chrono functions).

I'm know I'm supposed to make an interface, create a class to implement the interface for my actual implementation, and then mock out the interface on my tests. I'm trying to write an embedded application, so this level of indirection sounds too expensive for me.

What is the most efficient/performant way to incorporate system calls into Google Mock?

Lundquist answered 29/1, 2014 at 7:12 Comment(4)
Down vote? Why? This is a valid question. I'm seeking a better solution, and if I knew one I would never have posted. Surely, if this question is answered correctly, it will help several others with a similar circumstance.Lundquist
I think the downvotes are due to the (funny, but) opinionated nature of your phrasing. It is not relevant to the question if you hate singletons or what symptoms this level of indirection causes for you. I've attempted to make your question more neutral.Observatory
Added my 3 examples as runnable code at pastebin.com/0qJaQVcDStereochromy
Look below to Example 2 - Testable Using Instance Policy for the answer.Lundquist
S
7

No, you don't need to resort to mocked static classes - that's one of many options.

If you're in an embedded environment where a virtual dispatch is too much overhead, or the compiler/linker optimizer for that architecture does a really bad job, then you can try the following 3 ways to mock the platform calls.

Assume for simplicity that you want to mock a function in the std::this_thread namespace, like sleep_for(std::milliseconds).

Example 0 - Untestable Baseline

Without mocking, let's assume your code looks like this:

class untestable_class
{
public:

    void some_function()
    {
        if (must_sleep())
        {
            auto sleep_duration = std::chrono::milliseconds(1000);
            std::this_thread::sleep_for(sleep_duration);
        }
    }
};

You'd use that class like this:

void use_untestable_class()
{
    untestable_class instance;
    instance.some_function();
}

Due to the dependency on the standard library sleep_for function, you have a platform dependency that makes some_function difficult to unit test without actually making an integration test out of it.

Example 1 - Testable Using Static Policy

By telling our class to use a specific thread policy using a class template, we can abstract away the platform dependency in unit tests. The policy can either be static or instance - they both remove the need for a virtual dispatch at runtime and they are pretty easy for a compiler/linker to optimize.

In the static policy case, we have one "real" policy that depends on the platform:

struct system_thread_policy1
{
    static void sleep_milliseconds(long milliseconds)
    {
        auto sleep_duration = std::chrono::milliseconds(milliseconds);
        std::this_thread::sleep_for(sleep_duration);
    }
};

We also have a "mock" policy that we can control in unit tests:

struct mock_thread_policy1
{
    // Mock attributes to verify interactions.
    static size_t sleep_milliseconds_count;
    static size_t sleep_milliseconds_arg1;

    // Resets the mock attributes before usage.
    static void sleep_milliseconds_reset()
    {
        sleep_milliseconds_count = 0;
        sleep_milliseconds_arg1 = 0;
    }

    static void sleep_milliseconds(size_t milliseconds)
    {
        sleep_milliseconds_count++;
        sleep_milliseconds_arg1 = milliseconds;
    }
};

// This is needed with MS compilers to keep all mock code in a header file.
__declspec(selectany) size_t mock_thread_policy1::sleep_milliseconds_count;
__declspec(selectany) size_t mock_thread_policy1::sleep_milliseconds_arg1;

The production class that uses the policy takes the policy type as a template parameter and calls into its sleep_milliseconds statically:

template <typename thread_policy>
class testable_class1
{
public:

    void some_function()
    {
        if (must_sleep())
        {
            thread_policy::sleep_milliseconds(sleep_duration_milliseconds);
        }
    }

private:

    enum { sleep_duration_milliseconds = 1000 };
};

In production code, testable_class1 is instantiated using the "real" policy:

void use_testable_class1()
{
    testable_class1<system_thread_policy1> instance;
    instance.some_function();
}

In the unit test, testable_class1 is instantiated using the "mock" policy:

void test_testable_class1()
{
    mock_thread_policy1::sleep_milliseconds_reset();
    testable_class1<mock_thread_policy1> instance;
    instance.some_function();

    assert(mock_thread_policy1::sleep_milliseconds_count == 1);
    assert(mock_thread_policy1::sleep_milliseconds_arg1 == 1000);
    //assert("some observable behavior on instance");
}

Upsides of this method:

  • Features to test interactions, like the call count and argument checks above, can be added to the mock and be used to verify the class interactions unit tests.
  • The static call makes it very easy for the optimizer to inline the "real" call to sleep_for.

Downsides of this method:

  • The static state adds noise to the mock.
  • The static state needs to be reset in each unit test where it's used, since different unit tests will change that sticky state.
  • The static state makes it impossible to use the mock reliably if the test runner paralellizes unit tests, since different threads will fiddle with the same state, causing unpredictable behavior.

Example 2 - Testable Using Instance Policy

In the instance policy case, we have one "real" policy that depends on the platform:

struct system_thread_policy2
{
    void sleep_milliseconds(size_t milliseconds) const
    {
        auto sleep_duration = std::chrono::milliseconds(milliseconds);
        std::this_thread::sleep_for(sleep_duration);
    }
};

We also have a "mock" policy that we can control in unit tests:

struct mock_thread_policy2
{
    mutable size_t sleep_milliseconds_count;
    mutable size_t sleep_milliseconds_arg1;

    mock_thread_policy2()
        : sleep_milliseconds_count(0)
        , sleep_milliseconds_arg1(0)
    {
    }

    void sleep_milliseconds(size_t milliseconds) const
    {
        sleep_milliseconds_count++;
        sleep_milliseconds_arg1 = milliseconds;
    }
};

The production class that uses the policy takes the policy type as a template parameter, gets an instance of the policy injected in the contructor and calls into its sleep_milliseconds:

template <typename thread_policy>
class testable_class2
{
public:

    testable_class2(const thread_policy& policy = thread_policy()) : m_thread_policy(policy) { }

    void some_function() const
    {
        if (must_sleep())
        {
            m_thread_policy.sleep_milliseconds(sleep_duration_milliseconds);
        }
    }

private:

    // Needed since the thread policy is taken as a reference.
    testable_class2(const testable_class2&);
    testable_class2& operator=(const testable_class2&);

    enum { sleep_duration_milliseconds = 1000 };

    const thread_policy& m_thread_policy;
};

In production code, testable_class2 is instantiated using the "real" policy:

void use_testable_class2()
{
    const testable_class2<system_thread_policy2> instance;
    instance.some_function();
}

In the unit test, testable_class2 is instantiated using the "mock" policy:

void test_testable_class2()
{
    mock_thread_policy2 thread_policy;
    const testable_class2<mock_thread_policy2> instance(thread_policy);
    instance.some_function();

    assert(thread_policy.sleep_milliseconds_count == 1);
    assert(thread_policy.sleep_milliseconds_arg1 == 1000);
    //assert("some observable behavior on instance");
}

Upsides of this method:

  • Features to test interactions, like the call count and argument checks above, can be added to the mock and be used to verify the class interactions unit tests.
  • The instance call makes it very easy for the optimizer to inline the "real" call to sleep_for.
    • There's no static state in the mocks, which makes writing, reading and maintaining the unit tests easier.

Downsides of this method:

  • The instance state adds mutable noise to the mock.
  • The instance state adds noise to the client (testable_class2) - if the interactions don't need verifying, the policy can be passed by value in the constructor and most of the class goo goes away.

Example 3 - Testable Using Virtual Policy

This differs from the first 2 examples in the way that this relies on virtual dispatch, but leaves a likely possibility for the compiler/linker to optimize the virtual dispatch away if it can detect that the instance operated on is of base type.

First, we have the production base class that uses the "real" policy in a non-pure virtual function:

class testable_class3
{
public:

    void some_function()
    {
        if (must_sleep())
        {
            sleep_milliseconds(sleep_duration_milliseconds);
        }
    }

private:

    virtual void sleep_milliseconds(size_t milliseconds)
    {
        auto sleep_duration = std::chrono::milliseconds(milliseconds);
        std::this_thread::sleep_for(sleep_duration);
    }

    enum { sleep_duration_milliseconds = 1000 };
};

Second, we have the derived class that implements a "mock" policy in the virtual function (a kind of Template Method design pattern):

class mock_testable_class3 : public testable_class3
{
public:

    size_t sleep_milliseconds_count;
    size_t sleep_milliseconds_arg1;

    mock_testable_class3()
        : sleep_milliseconds_count(0)
        , sleep_milliseconds_arg1(0)
    {
    }

private:

    virtual void sleep_milliseconds(size_t milliseconds)
    {
        sleep_milliseconds_count++;
        sleep_milliseconds_arg1 = milliseconds;
    }
};

In production code, testable_class3 is just instantiated as itself:

void use_testable_class3()
{
    // Lots of opportunities to optimize away the virtual dispatch.
    testable_class3 instance;
    instance.some_function();
}

In the unit test, testable_class3 is instantiated using the "mock" derived class:

void test_testable_class3()
{
    mock_testable_class3 mock_instance;
    auto test_function = [](testable_class3& instance) { instance.some_function(); };
    test_function(mock_instance);

    assert(mock_instance.sleep_milliseconds_count == 1);
    assert(mock_instance.sleep_milliseconds_arg1 == 1000);
    //assert("some observable behavior on mock_instance");
}

Upsides of this method:

  • Features to test interactions, like the call count and argument checks above, can be added to the mock and be used to verify the class interactions unit tests.
  • The base class virtual call on "itself" makes it possible for the optimizer to inline the "real" call to sleep_for.
  • There's no static state in the mocks, which makes writing, reading and maintaining the unit tests easier.

Downsides of this method:

  • The base class cannot be marked final (C++11), since it must be allowed to be inherited from, and this might affect the rest of the class design if there is more complexity than the simple example above.
  • The compiler/linker might be subpar or simply unable to optimize the virtual dispatch.

Test Run

All of the above can be tested with this:

int _tmain(int argc, _TCHAR* argv[])
{
    test_testable_class1();
    test_testable_class2();
    test_testable_class3();

    return 0;
}

and the complete runnable example is at http://pastebin.com/0qJaQVcD

Stereochromy answered 29/1, 2014 at 9:52 Comment(12)
Great post, thanks! One note to this specific example where we mock sleep_for - when mocking out sleep I would say that any potential call overhead is totally irrelevant, since we are going to sleep for milliseconds anyway :-)Wacke
Thank you for the in-depth response! Example 2 - Testable Using Instance Policy is the best answer in the scope of Google Mock. Also, in example #3, doesn't the virtual method on the base class have to be marked protected instead of private to passed to the subclass?Lundquist
@Zak: "in example #3, doesn't the virtual method on the base class have to be marked protected instead of private to passed to the subclass?" - No. By making it private in the base class, you prevent the derived class to call the base class version of it, since only the base has access to it. Also, as I said, "and the complete runnable example is at pastebin.com/0qJaQVcD" - it really builds and runs :)Stereochromy
testable_class3 has a private virtual method - why? mock_testable_class3 does not inherit this method, correct? Can you update your post to explain why those private methods need to be virtual or what purpose that serves?Lundquist
@Zak: "mock_testable_class3 does not inherit this method, correct?" - Wrong. When the instance is a mock_testable_class3 and the base class testable_class3 calls sleep_milliseconds, the overridden mock_testable_class3::sleep_milliseconds is called and the private makes it impossible to call testable_class3::sleep_milliseconds from mock_testable_class3::sleep_milliseconds.Stereochromy
As I have begun to digest the code more and more, I find myself curious about the following line from Example 2 const thread_policy& m_thread_policy; Is this holding a reference to an object created on the stack, then later popped off? Should it instead be const thread_policy m_thread_policy; to force a copy of the object to be created?Lundquist
@Zak: If you're not interested in verifying the interaction between the tested class and the provided mock thread policy, then you can have the member variable as just a thread_policy m_thread_policy instead of a const thread_policy& m_thread_policy, but const thread_policy m_thread_policy is over the top, not needed. To be able to verify that the tested class calls in to your provided mock as expected, yo need to be able to query the mock you provided, hence why it's kept as a reference instead of a value.Stereochromy
@Zak: Look at line 168 and 169 of pastebin.com/0qJaQVcD - that wouldn't be possible if you kept the member as a value instead of as a reference.Stereochromy
I see that it's crucial for your test, and I have confirmed it must be there for Google Mock to work correctly. However, it is my actual implementation I fear for. If system_thread_policy2 is created on the stack during the call to the constructor, what guarantees the validity of the reference after the constructor returns and its stack frame is removed (popped) from the stack?Lundquist
@Zak: If you don't provide a policy in the constructor, it'll default to the "real" policy (testable_class2(const thread_policy& policy = thread_policy()) : m_thread_policy(policy) { }) and since you bind that RValue to a const reference, it's guaranteed by the standard to work.Stereochromy
AWESOME! Can you please edit your post, and put a //code comment (or an italicized note) indicating the part of the standard that guarantees this behavior. You have been so much help, thank you so much!Lundquist
@Zak: Q1 & A1 at herbsutter.com/2008/01/01/… - if you doubt Herb Sutter, then I'm sad to say I have nothing more to add.Stereochromy
H
0

First of all, virtual dispatch is not that expensive, therefore you may be doing micro optimization. Even for an embedded platform.

If you really want to avoid virtual dispatch, you could use static polymorphism, with templates, and inject system calls through template parameter. Something like this :

struct SysCallCaller
{
    static void doCall()
    {
        // really execute system call
    }
    private:
    SysCallCaller();
};

struct SysCallMock
{
    static void doCall()
    {
        // mock system call
    }
};

template < typename SysCallType >
struct MyClass
{
    void Foo()
    {
        SysCallType::doCall();
    }

};

int main()
{
#ifdef CALL_MOCK
    MyClass< SysCallMock > obj;
#else
    MyClass< SysCallCaller > obj;
#endif

    obj.Foo();
}

Above all, try to avoid singletons. They are just hidden global variables.

Heeltap answered 29/1, 2014 at 7:20 Comment(8)
Your example uses two additional classes - this is exactly what I want to not do. I'm not objecting to using a v-table, I'm objecting to the wasted memory. I'm dealing with systems who's RAM is measured in KB.Lundquist
@Lundquist Ok, now it is only one, and such calls are most likely optimized away.Etymologize
You are making static classes... This is one step away from a singleton, only you are not blocking the constructor. Why is your solution any better?Lundquist
@Lundquist There is no such thing as static class in c++. The method in template class requires static method. You didn't want to create the object to save some memory.Etymologize
That is right, however struct == class. Therefore you are, in effect, making static classes (or singletons) - whatever you want to call them.Lundquist
@Lundquist A class with static method is not a singleton. Static method is same as a function. If you take a look, no object is created. Singleton creates only one instance of a class.Etymologize
Fair enough. Is there an easy way to ensure there will be no instance of this class? Is it good enough to make the constructors private?Lundquist
let us continue this discussion in chatLundquist

© 2022 - 2024 — McMap. All rights reserved.