当前位置: 首页 > 工具软件 > Gmock > 使用案例 >

纠结了好几天的GMock学习小结

张逸清
2023-12-01

这周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

 类似资料: