这周project中遇到了用GMock实现的unit test,看了doc依旧很迷,今天终于想明白了,在此记录一下。
首先,为什么需要GMock,只用GTest不行吗?
GTest作为unit test很好理解,即为test一个函数end to end的输入输出对不对,例如
int Factorial(int n); // Given an input n, returns the result of n!.
EXPECT_EQ(Factorial(2), 2);
EXPECT_EQ(Factorial(1), 1);
可以测试Factorial()函数的结果是否正确。
但是GTest只可test函数的输出,无法test函数内部的情况。对于以下例子
class Oiginal
{
void InnerFuncA();
void InnerFuncB();
bool OuterFunc(string name, bool flg)
{
cout << "My name is: " << name <<endl;
if(flg)
{
InnerFuncA();
}
else
{
InnerFuncB();
}
return random() > 0.5 ? true : false;
}
};
我们想check当flg == true时,调用的是InnerFuncA(),flg == false时,调用的是InnerFuncB()。这种OuterFunc()内部的情况就无法通过end-to-end test来check了。(当然比较笨的方法就是手动查看log。)
GMock就可以解决这个问题,例如
class MockedOne : public Original
{
MOCK_METHOD2(OuterFunc, bool(string name, bool flg));
MOCK_METHOD0(InnerFuncA, void());
MOCK_METHOD0(InnerFuncB, void());
}
MockedOne mock_object;
EXPECT_CALL(mock_object, InnerFuncA());
mock_object.OuterFunc("mocked", true);
Mock class 'MockedOne'表示mock了base class的function。例如,当你call OuterFunc()时,你以为call的是original function (from base class),实际上call的是mocked function (from derived class)。
EXPCET_CALL表示这段code中会call InnerFuncA(),如果没有call InnerFuncA(), test会fail。EXPCET_CALL()只是expect函数的某种behavior,算是一个condition check,并未做任何实际的事情。而下一行的mock_object.OuterFunc();才是真正做了事情去call InnterFuncA()的。没有这一行mock_object.OuterFunc(),test会fail,因为InnerFuncA()没有被call。
除了仅仅expect某个函数被call之外,我们还可以expect这个函数被call几次,or至多,至少call几次。
EXPECT_CALL(mock_object, InnerFuncA())
.Times(3);
表示InnterFuncA()会被恰好call三次,多了少了都会test fail。
我们也可以expect函数在什么情况下被call,例如call的时候传的是什么参数。
EXPECT_CALL(mock_object, OuterFunc(_, true));
MockedOne mock_object;
mock_object.OuterFunc("mocked", true);
OuterFunc(_, true)即表示一个matcher,表示当OuterFunc()被call时,传的第一个参数可以是任意string,我们不care,但是第二个参数必须是true。如果我们将上述code第三行换成
mock_object.OuterFunc("mocked", false);
那么test会fail,因为和expectation不一致。
matcher可以扩展到更加general的情形,比如argument >= some threshold, argument has a sub string of "some_string", etc.
当然实际implementation还有很多detail,比如MOCK_METHDD()括号内的函数需要时virtual,才可以识别是call base class还是derived class。在此只是讲述high level idea,并未test code是否compile 。o(╯□╰)o
以上用法都是expect某个函数的某个behavior,这些behavior是由函数本身或者caller side的code决定的, 即使没有EXPECT_CALL(...) 这些code,该出现的call,该match的argument都仍然hold。
但是当我看到下述usage,就很迷了。
EXPECT_CALL(mock_object, OuterFunc("name", true))
.Times(1)
.WillOnce(Return(true));
mock_object.OuterFunc("name", true);
看起来像是OuterFunc()被call一次,且第一次的return value是true?但是在相关的example中,并未有任何迹象表明这个函数会一直return true。
这里面WillOnce(Return(true))定义的是action,并非是behavior。二者区别为何?action是test author manually定义的,比如,虽然我不知道OuterFunc()会返回什么,但是在这个mock test里面我只想让它返回true。所以在这个code snippet里面,即使我把Return(true)换成Return(false)也不会test fail。但是,behavior是我们expect函数的行为,并非是test author控制的(when 除了expect中check condition之外的code给定时,若test author删了mock_object.OuterFunc("name", true)这一行当我没说)。所以如果我把上述的Times(1)换成Times(0)就会使得test fail,因为我们的code call了OuterFunc()一次。
这种Action就可以解释了为什么可以节省cost。对于某些函数,我们得到它的返回值可能需要很复杂的simulation,例如读取数据库,网络上的发包收包等。如果我们要实现这些simulation,首先我们要网数据库里inject data,然后再用各种SQL之类的命令根据key word查找,再将查找结果作为返回值。例如:
void ProcessData()
{
// The function QueryFromDb() is to search records in db with the key word "key_word".
vector<string> filtered_data = QueryFromDb("key_word");
// Process filtered_data.
ComputeDistribution(filtered_data);
}
但是如果每次unit test,我们都经过向DB inject data, SQL query,return searched results这一系列复杂的simulation操作,会产生如下weakness:
1. 读取数据库会使得unit test很慢。
2. 如果数据库的process结果不对,会影响当前的ProcessData()的unit test。 即使ProcessData()的logic是正确的,结果依然不对。
3. 如果这个数据库是production里面使用的数据库,会占据production job的resource。
所以GMock的作用是,假装我进行了这一些列复杂的操作,然后得到了一个return value,但这个return value是我mock的,并不是真正由original source data产生的。例如:
EXPECT_CALL(mock_object, QueryFromDb(_))
.WillOnce(Return({"a", "b", "c"}));
MockedOne mock_object;
mock_object.ProcessData();
如此,在call mock_object.ProcessData()的时候,在ProcessData()内部我们会试图去call QueryFromDb("key_word"),此时Mocked method会直接返回{"a", "b", "c"}而不是真正地去DB search "key_word"。因此我们就可以unit test ProcessData()的logic了。相当于QueryFromDb("key_word")是一个black box,我们只care其return value,无论这个return value是真正地从数据库里得到的,还是faked one。
MOCK_METHOD(method) means when we call method, we are actually calling the mocked one, so the return value is mocked, instead of the real, original one.
另外一个常见的(同样纠结了很久的)用法是Invoke,例如:
using ::testing::Invoke;
double Distance(Unused, double x, double y) { return sqrt(x*x + y*y); }
// ...
EXPECT_CALL(mock_object, Foo("Hi", _, _)).WillOnce(Invoke(Distance));
我本来以为Invoke(Distance)是由Foo函数内部本身call的Distance(),但是Distance()和Foo完全independent啊,而且为啥要求两者argv list都要一样呢?
后来才明白Distance是test author manually call的,如果没有Invoke,Distance()就会无人理会。Invoke的作用是,在Foo因缘际会被call的时候可以check一下参数or计算一些辅助的value or just print log。这也就解释了为啥会要求二者argument list一样or consistent。Invoke()同时也可以用于call helper function。
以上即为GMock的rationale and basic usage。
主要的Confusing:
Expected behavior or the action that is manually set by a test author.
我真的看了Google官方Code Book好久都没弄清楚,例如对于Invoke()的解释
Invoke(f) | Invoke f with the arguments passed to the mock function, where f can be a global/static function or a functor. |
我以为Invoke f的主语是EXPECT_CALL括号里面的mocked function,现在发现主语是test author。
不知有无其他小伙伴被这么confuse过。┭┮﹏┭┮
参考资料:
Google Mock(Gmock)简单使用和源码分析——简单使用 https://blog.csdn.net/breaksoftware/article/details/51384083
转一篇小亮同学的google mock分享 https://www.cnblogs.com/welkinwalker/archive/2011/11/29/2267225.html
各位都是怎么进行单元测试的? - 陈萌萌的回答 - 知乎 https://www.zhihu.com/question/27313846/answer/130954707
GMock offical doc: https://github.com/google/googletest/blob/master/googlemock/docs/cheat_sheet.md