Gtest:在构造函数中模拟免费功能

时间:2019-02-12 13:11:31

标签: c++ unit-testing c++11 mocking googletest

我阅读了很多与Gtest模拟相关的文档(例如https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md,“模拟自由函数”),但是找不到以下问题的解决方案:

  

source.cpp

H::H()
{
    // some code1
    if (to_be_mocked(id) != 0) { // some code2 }
    // some code3
}

H& H::get_instance()
{
    static H s;
    return s;
}
  

unit_test.cpp

#include "gtest/gtest.h"
#include "gmock/gmock.h"

#include "source.h"

TEST(Source, Constructor)
{
    // What to write here to mock function "to_be_mocked"?
    H& inst = H::get_instance();
}

int main(int argc, char** argv)
{
  testing::InitGoogleMock(&argc, argv);
  return RUN_ALL_TESTS();
}

因此,我需要在H的构造函数和模拟函数to_be_mocked中测试整个代码,该函数在不同的转换单元中定义。我如何从unit_test.cpp做到这一点?

1 个答案:

答案 0 :(得分:1)

Dependency injection(DI)来解救!

DI是模拟的关键推动因素。具体来说,您可以使用Strategy pattern将依赖项注入到该对象中,以便可以在测试时将其排除。

选项1:构造函数注入

最简单的方法是将函子传递给构造函数,并在您当前正在调用的地方to_be_mocked()进行调用。

在这种情况下,您的课程如下所示:

class H 
{
    std::function<bool(int)> _to_be_mocked;
public:
    H( std::function<bool(int)> fn ) 
        : _to_be_mocked( std::move(fn) ) 
    {
        uses_mockable( 42 ); 
    }

    void uses_mockable( int id ) 
    {
        if( _to_be_mocked(id) ) { ... }
    }

    ...
};

void MyTest() 
{
    auto my_mock_fn = ...;
    auto h = H{ my_mock_fn };

    // Set expectations to assert that my_mock_fn is used correctly
    // and that the caller behaves properly in response to its return values
}

DI在全局变量/单例中不能很好地发挥作用,因为您不能在构造时(轻松地)注入依赖项,这是不鼓励它们的原因之一。

选项2:属性注入

如果您无法将单例更改为常规实例或单独控制其初始构造并可以注入依赖项,则可以使用基于属性的注入,在其中公开公开该函子(或通过诸如{ {3}}),然后在需要时进行设置。

在这种情况下,您的课程如下所示:

class H 
{
    std::function<bool(int)> _to_be_mocked;
public:

    H() 
        : _to_be_mocked( to_be_mocked ) // Use stand-alone function for default
    { /* can't use mock here */ }

    // Could restrict accessibility here with Attorney-Client idiom or friendship
    void set_to_be_mocked( std::function<bool(int)> fn ) 
    { 
        _to_be_mocked = std::move( fn ); 
    }

    void uses_mockable( int id ) 
    {
        if( _to_be_mocked( id ) ) { ... }
    }

    ...
};

void MyTest() 
{
    auto my_mock_fn = ...;
    auto& h = H::get_instance();
    // ...
    h.set_to_be_mocked( my_mock_fn );

    // Set expectations to assert that my_mock_fn is used correctly
    // and that the caller behaves properly in response to its return values
}

如果您需要在构造函数中调用调用 _to_be_mocked(),则此方法将不起作用,因此您必须使用前一种方法(首选)或使用选项3。

选项3:按全局变量计算的穷人DI

如果您不能使用上述任何一种方法,则可以通过使用Yet Another Global(他们说罪恶生罪)来使用“穷人的DI”。在您的情况下,无需更改调用代码的一种方法是将to_be_mocked()重命名为to_be_mocked_impl()之类,并创建一个名为to_be_mocked的全局仿函数:

bool to_be_mocked_impl( int id ) { ... } // used to be called to_be_mocked(int)

// Global instance that can be swapped out for testing.
// Defaults to the normal runtime function.
// Might use a raw function pointer instead of a std::function if you prefer.
auto to_be_mocked = std::function<bool(int)>{ to_be_mocked_impl };

class H 
{
public:
    H() 
    {
        uses_mockable( 42 ); 
    }

    void uses_mockable( int id ) 
    {
        if( to_be_mocked(id) ) { ... }
    }

    ...
};

void MyTest() 
{
    auto my_mock_fn = ...;
    to_be_mocked = my_mock_fn; // Sets global ... blah!!

    auto& h = H::get_instance();

    // Set expectations to assert that my_mock_fn is used correctly
    // and that the caller behaves properly in response to its return values
}

这使情况与全局变量/单例结合在一起,因此除非您出于不合理的原因而不得不这样做,否则我不建议这样做。

PS,有一个Attorney-Client idiom谈论实验性的episode of CppCast on Dependency Injection可能在这里有用。