目录
- MOCK_METHOD
- mock方法的访问属性
- mock非虚函数
- mock自由函数
- Nice/Strict/Naggy
- mock方法简化参数
- mock具体类的替代方法
- 代理给fake
mock是用来模拟对象,隔离边界的一种测试方法,以便在开发阶段不需要依赖第三方或其他依赖项可以进行独立的测试。
MOCK_METHOD
使用MOCK_METHOD 宏生成mock方法。MOCK_METHOD 宏有三到四个参数,前三个参数为函数签名分成的三部分(返回值,函数名,参数),第四个参数为以下一个或多个,逗号分隔:
- const:使这个mock方法成为一个const函数,如果重写的函数时const函数,则这个是必须的。
- override:同C++中的override,如果是重写虚函数,建议加上该关键字
- noexpect:如果重写的函数时noexpect,那mock方法也是要加上的
- Calltype(…):只在windows上有效
- ref(…):TODO
MOCK_METHOD 形如:MOCK_METHOD(int, my_func, (int x, string y), (const, override));
mock方法的访问属性
不管base class中函数的属性是哪种,mock方法必须都是public。因为这样ON_CALL和EXPECT_CALL可以在mock类外访问。
mock非虚函数
mock非虚函数的mock类除了使用MOCK_METHOD 定义的mock函数签名一样外,其他和原类没有任何关系了。
class ConcretePacketStream {
public:
void AppendPacket(Packet* new_packet);
const Packet* GetPacket(size_t packet_number) const;
size_t NumberOfPackets() const;
};
class MockPacketStream {
public:
MOCK_METHOD(const Packet*, GetPacket, (size_t packet_number), (const));
MOCK_METHOD(size_t, NumberOfPackets, (), (const));
};
实际上相当于重新写了不相关的mock类,只是保持了类函数签名一样。在mock类中可以按实际需要mock函数,不需要mock原类中的所有函数。
- mock非虚函数需要在编译期就要决定是哪个对象,这一点不像虚函数。解决这个问题的一种方法是模板化你的代码。在生产代码中使用原类,在测试代码中使用mock类作为模板参数传入(策略模式)(这种使用场景需要再了解TODO)
mock自由函数
无法mock一个自由函数(不是类的成员函数都叫自由函数),但是可以通过以下方式实现:
- 实现一个接口类,在继承这个接口类的函数中调用这个自由函数以实现mock它的功能。
class FileInterface {
public:
...
virtual bool Open(const char* path, const char* mode) = 0;
};
class File : public FileInterface {
public:
...
bool Open(const char* path, const char* mode) override {
return OpenFile(path, mode);
}
};
- 通过mock std::function实现。
::testing::MockFunction<R(T1, ..., Tn)>
有两个mock方法:R Call(T1, ..., Tn)
std::function<R(T1, ..., Tn)> AsStdFunction()
- 实现步骤:
- 创建一个MockFunction对象,签名和要mock的free function保持一致
- 在期望EXPECT_CALL中使用Call
- 使用AsStdFunction()实现调用mock函数的调用。
- 用途:
- 可以mock自由函数
- 可以mock回调函数,作为参数传递
示例
TEST(FooTest, RunsCallbackWithBarArgument) {
MockFunction<int(string)> mock_function; // 声明MockFunction对象,自由函数的签名为int xx(string);
EXPECT_CALL(mock_function, Call("bar")).WillOnce(Return(1)); // 这里指定期望的调用
std::function<int(string)> f = mock_function.AsStdFunction(); // 取出mock函数并调用
EXPECT_EQ(f("bar"), 11);
}
Nice/Strict/Naggy
TEST(...) {
MockFoo mock_foo;
EXPECT_CALL(mock_foo, DoThis());
... code that uses mock_foo ...
}
如果mock函数没有EXPECT_CALL但是被调用了。这种被称为不感兴趣的调用,会有告警信息。如下:
如果要忽略这种信息,可以使用NiceMock<MockFoo>
代替mock_foo
:
NiceMock<MockFoo>
是MockFoo的一个子类。以下方式调用完之后没有uninsterestring信息。
using ::testing::NiceMock;
TEST(test_override, mock_override02) {
NiceMock<MockFoo> foo; // 使用NiceMock<MockFoo>代替MockFoo
EXPECT_CALL(foo, Add(::testing::_))
.Times(1)
.WillRepeatedly(testing::Return(12));
EXPECT_EQ(foo.DoThis(), 12);
}
StrictMock 的用法和NiceMock一样,期望所有的不感兴趣的调用失败。(看输出结果和NiceMock基本一样,不知道有啥区别?)
注意事项:
NiceMock<MockFoo>
andStrictMock<MockFoo>
只作用于使用MOCK_METHOD 宏定义的mock函数中,如果mock函数定义在MockFoo的base类中,NiceMock或StrictMock不会影响它。- 如果MockFoo 类的析构是非虚函数,
NiceMock<MockFoo>
和StrictMock<MockFoo>
可能不会正常工作
mock方法简化参数
有时候要mock的方法有很多入参,这些入参大部分不需要,可以通过以下示例中的方式进行在mock函数中进行简化
class LogSink {
public:
virtual void send(int severity, const char* full_filename,
const char* base_filename, int line,
const int* tm_time,
const char* message, size_t message_len) = 0;
};
class ScopedMockLog : public LogSink {
public:
void send(int severity, const char* full_filename,
const char* base_filename, int line, const int* tm_time,
const char* message, size_t message_len) override {
Log(severity, full_filename, base_filename);
std::cout << "ScopedMockLog::send" << std::endl;
}
MOCK_METHOD(void, Log, (int severity, const string& file_path, const string& message));
};
TEST(test_simplify, mock_simplify) {
mock_simplify::ScopedMockLog log;
EXPECT_CALL(log, Log(1, "a.txt", "bbb")).WillOnce([](int x, const string& file_path, const string& message){
std::cout << "x: " << x << ", file_path is: " << file_path << ", message: " << message << std::endl;
});
log.Log(1, "a.txt", "bbb");
}
分析:接口类LogSink 的send方法中我们只用到severity,file_path和message这三个参数,其他不需要,可以通过在派生的mock类中实现这个虚函数而不是直接mock它。并定义一个只需要这三个参数的新函数(通过MOCK_METHOD定义)在后续测试程序中使用就OK了。
mock具体类的替代方法
通过给具体类定义一个父接口类的方式