所谓的 mock 方法,是单元测试中常见的一种测试方式,用来模拟对象、隔离边界等,例如单元测试时模拟三方接口,这样服务可以独立测试;开发阶段不需要依赖其它类的开发进度等等。
在 C++ 中,比较常用的是 Google 的 GMock 工具,可以用来模拟构造接口,并返回 mock 数据。
这里我们从一个简单的示例开始,一步步详细介绍其使用方法,以及一些常用的技巧。
简介
Mock 工具用在测试驱动 (Test-Driven Development) 的开发模式中经常使用,Google Mock 是在 2008 年推出的一套针对 C++ 的 Mock 工具。
GMock 开始是作为一个独立的项目开发维护的,后来和 GTest 合并,统一在 GitHub GTest/GMock 仓库上维护,所以其安装步骤与 GTest 的安装步骤相同,可以参考 C++ 单测工具 gtest 使用详解 中的详细介绍。
示例
假设我在和一个名叫张三的同事共同开发一个产品,其中他会负责对不同的图形计算面积,而我需要根据面积来计算该图形的价值 (就简单乘以一个固定系数),所以,我们先定义了一个接口如下。
class Shape {
public:
virtual double Area(void) = 0;
};
张三会根据不同的类型进行计算,例如对于四边形来说为。
class Rectangle: public Shape {
private:
double width, height;
public:
// Simple Constructor
Rectangle(double w, double h) {
width = w;
height = h;
}
// Destructor
~Rectangle() { } // Do nothing
double Area(void) {
return width * height;
}
};
然后我实现了计算价值的代码如下。
double GetPrice(class Shape &s) {
return s.Area() * 2;
}
正常的调用流程应该是如下。
int main(void) {
Rectangle rect(3, 4);
std::cout << GetPrice(rect) << std::endl;
return 0;
}
因为张三需要完成正方形、圆形、三角形等相关形状的功能开发,工作量要大很多,为了不阻塞自己的开发,此时就可以通过 gmock 来模拟 Shape 接口返回的数据,如下为完整的代码。
#include <gtest/gtest.h>
#include <gmock/gmock.h>
class Shape {
public:
virtual double Area(void) = 0;
};
double GetPrice(class Shape &s) {
return s.Area() * 2;
}
class ShapeMock: public Shape {
public:
MOCK_METHOD0(Area, double());
};
TEST(ShapeTest, Rectangle) {
ShapeMock rect;
EXPECT_CALL(rect, Area()).WillRepeatedly(testing::Return(12));
EXPECT_EQ(24, GetPrice(rect));
}
int main(int argc, char **argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
也就是说,在模拟的 rect
对象中,在调用 Area()
接口时会一直返回 12
这个值。
执行流程
在通过 gmock 进行 Mock 时一般步骤如下:
- 定义一个 Mock 类,在类中需要通过类似
MOCK_METHOD
的宏定义接口; - 测试时新建一个 Mock 对象,用来模拟接口定义的行为;
- 通过 gmock 提供的接口设置 Mock 对象需要执行的动作;
- 调用 Mock 对象返回的数据,检查是否与预期相符。
其中第 3 步定义了 Mock 对象应该返回的结果,通过 gmock 提供的 API 接口来模拟预期的行为,而当 mock 对象被销毁时会自动检查是否所有的内容 (期望行为) 都检查过了。
期望行为
这也是在单元测试中使用 Mock 方法时最关键的动作,可以通过如下语法进行定义。
EXPECT_CALL(mock_object, method(matcher1, matcher2, ...))
.With(multi_argument_matcher)
.Times(cardinality)
.InSequence(sequences)
.After(expectations)
.WillOnce(action)
.WillRepeatedly(action)
.RetiresOnSaturation();
简单介绍下上面的语法:
mock_object
是创建的 mock 对象,而method
对应了方法名称,例如上面的Area
方法,剩余的matcher
是参数信息;Times(cardinality)
之前定义的方法运行几次;
如下是一个简单的示例。
EXPECT_CALL(mock, Area())
.Times(testing::AtLeast(5))
.WillOnce(testing::Return(100))
.WillOnce(testing::Return(150))
.WillRepeatedly(testing::Return(200))
可以解释为,在调用 mock
的 Area()
方法时,至少会被调用 5 次,第一次被调用返回 100 ,第二次被调用返回 150 ,以后每次会返回 200 。
参考
在 GitHub gmock README.md 中包含了很多有用的参考信息。