使用 Google Mock 进行 C++ 高性能单元测试?

2024-04-23

我正在使用 Google Mock,并且正在努力模拟 C++ 系统调用(特别是 C++11 chrono 函数)。

我知道我应该创建一个接口,创建一个类来实现我的实际实现的接口,然后在我的测试中模拟该接口。我正在尝试编写一个嵌入式应用程序,因此这种间接级别对我来说听起来太昂贵了。

将系统调用合并到 Google Mock 中最有效/性能最高的方法是什么?


不,您不需要诉诸模拟静态类 - 这是众多选项之一。

如果您处于虚拟调度开销太大的嵌入式环境中,或者该架构的编译器/链接器优化器做得非常糟糕,那么您可以尝试以下 3 种方法来模拟平台调用。

为简单起见,假设您想要模拟一个函数std::this_thread命名空间,例如sleep_for(std::milliseconds).

示例 0 - 无法测试的基线

在没有模拟的情况下,我们假设您的代码如下所示:

class untestable_class
{
public:

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

您可以像这样使用该类:

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

由于对标准库的依赖sleep_for函数,你有一个平台依赖性,使得some_function如果不实际进行集成测试,就很难进行单元测试。

示例 1 - 可使用静态策略进行测试

通过告诉我们的班级使用特定的线程策略使用类模板,我们可以抽象出单元测试中的平台依赖性。该策略可以是静态的,也可以是实例的 - 它们都消除了运行时虚拟调度的需要,并且编译器/链接器很容易优化。

In the static政策案例,我们有一项取决于平台的“真实”政策:

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

我们还有一个可以在单元测试中控制的“模拟”策略:

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;

使用策略的生产类将策略类型作为模板参数并调用其sleep_milliseconds静态地:

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 };
};

在生产代码中,testable_class1使用“真实”策略实例化:

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

在单元测试中,testable_class1使用“mock”策略实例化:

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");
}

这种方法的优点:

  • 测试交互的功能,例如呼叫计数 and 参数检查上面的内容可以添加到模拟中并用于验证类交互单元测试。
  • 静态调用使优化器很容易内联“真实”调用sleep_for.

这种方法的缺点:

  • 静态会给模拟增加噪音。
  • 静态状态需要在使用它的每个单元测试中重置,因为不同的单元测试会改变该粘性状态。
  • 如果测试运行器并行化单元测试,则静态状态使得无法可靠地使用模拟,因为不同的线程将摆弄相同的状态,从而导致不可预测的行为。

示例 2 - 可使用实例策略进行测试

In the instance政策案例,我们有一项取决于平台的“真实”政策:

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);
    }
};

我们还有一个可以在单元测试中控制的“模拟”策略:

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;
    }
};

使用策略的生产类将策略类型作为模板参数,获取注入到构造函数中的策略实例并调用其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;
};

在生产代码中,testable_class2使用“真实”策略实例化:

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

在单元测试中,testable_class2使用“mock”策略实例化:

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");
}

这种方法的优点:

  • 测试交互的功能,例如呼叫计数 and 参数检查上面的内容可以添加到模拟中并用于验证类交互单元测试。
  • The instance call makes it very easy for the optimizer to inline the "real" call to sleep_for.
    • 模拟中没有静态,这使得编写、阅读和维护单元测试变得更加容易。

这种方法的缺点:

  • 实例状态向模拟添加了可变噪声。
  • 实例状态给客户端增加了噪音(testable_class2) - 如果交互不需要验证,则可以在构造函数中按值传递策略,并且大多数类 goo 都会消失。

示例 3 - 可使用虚拟策略进行测试

这与前两个示例的不同之处在于,它依赖于虚拟分派,但如果编译器/链接器可以检测到所操作的实例是基本类型,则它可能会优化虚拟分派。

首先我们有生产base在非纯虚函数中使用“真实”策略的类:

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 };
};

其次,我们有derived在虚拟函数中实现“模拟”策略的类(一种模板方法设计模式):

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;
    }
};

在生产代码中,testable_class3只是实例化为自身:

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

在单元测试中,testable_class3使用“模拟”派生类实例化:

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");
}

这种方法的优点:

  • 测试交互的功能,例如呼叫计数 and 参数检查上面的内容可以添加到模拟中并用于验证类交互单元测试。
  • 对“自身”的基类虚拟调用使优化器可以内联“真实”调用sleep_for.
  • 模拟中没有静态,这使得编写、阅读和维护单元测试变得更加容易。

这种方法的缺点:

  • 基类不能被标记final(C++11),因为必须允许继承它,如果比上面的简单示例更复杂,这可能会影响类设计的其余部分。
  • 编译器/链接器可能不合格或者根本无法优化虚拟调度。

Test Run

以上所有内容都可以用以下方法进行测试:

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

    return 0;
}

完整的可运行示例位于http://pastebin.com/0qJaQVcD http://pastebin.com/0qJaQVcD

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Google Mock 进行 C++ 高性能单元测试? 的相关文章

随机推荐