我阅读了很多与Gtest模拟相关的文档(例如,https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md,“Mocking Free Functions”),但找不到以下问题的解决方案:
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
做到这一点?
Dependency injection(DI)救援!
DI是嘲弄的关键推动因素。具体来说,您可以使用Strategy pattern将依赖项注入此对象,以便您可以在测试时将其分出。
最简单的版本是将一个仿函数传递给构造函数,并调用当前调用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不适合全局/单身,因为你不能(轻松地)在施工时注入依赖关系,这也是他们气馁的原因之一。
如果您不能将单例更改为常规实例或单独控制其初始构造,您可以在其中注入依赖项,那么您可以使用基于属性的注入来公开(或选择性地通过类似Attorney-Client idiom)然后公开函数在需要时设置它。
在这种情况下,您的类看起来像:
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。
如果你不能使用上述任何一种方法,你可以使用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,有一个episode of CppCast on Dependency Injection谈到可能在这里有用的实验性[Boost.]DI library。