单元测试其二:gmock

单元测试其二:gmock

the dummy guide

当你和你的小伙伴合作设计一个原型系统。你的模块Painter需要依赖伙伴的模块Turtle,但是因为他比较懒,只是编写了接口,还未实现。这个时候你本该焦头烂额,但是有了gmock,你可以模拟Turtle的行为。

伙伴的turtle模块有诸如向上移动,转弯,获取坐标之类的操作。

第一步要做的是定义一个模拟类,然后继承要mock的类Turtle:

//your module
class Painter {
 public:
  Painter(cosnt Turtle turtle) {
    t = turtle;
  bool DrawCircle(int x, int y, int radius) {
  private:
  Turtle t;
class Turtle {
  virtual ~Turtle() {}
  virtual void PenUp() = 0;
  virtual void PenDown() = 0;
  virtual void Forward(int distance) = 0;
  virtual void Turn(int degrees) = 0;
  virtual void GoTo(int x, int y) = 0;
  virtual int GetX() const = 0;
  virtual int GetY() const = 0;
class Painter
class MockTurtle : public Turtle {
 public:
  MOCK_METHOD(void, PenUp, (), (override));
  MOCK_METHOD(void, PenDown, (), (override));
  MOCK_METHOD(void, Forward, (int distance), (override));
  MOCK_METHOD(void, Turn, (int degrees), (override));
  MOCK_METHOD(void, GoTo, (int x, int y), (override));
  MOCK_METHOD(int, GetX, (), (const, override));
  MOCK_METHOD(int, GetY, (), (const, override));
};

MOCK_METHOD中的参数依次是 返回值,函数名,参数列表。注意模拟函数 不一定是virtual 的,事实上几乎任何函数都可以模拟。

注意:在老的版本(比如gtest 1.6)并未实现MOCK_METHOD,只能用MOCK_METHODx代替,这里x是参数数目,从0开始。不过,真心建议你的团队使用最新版的 gmock
这里总结一下两者的不同点:
MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHODx(MethodName, ReturnType (Args...));


然后你就可以高高兴兴的进行你代码的测试了,例如你想让turtle向下移动,再画一个圆。

#include "path/to/mock-turtle.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
using ::testing::AtLeast;                         // #1
TEST(PainterTest, CanDrawSomething) {
  MockTurtle turtle;                              // #2
  EXPECT_CALL(turtle, PenDown())                  // #3
      .Times(AtLeast(1));
  Painter painter(&turtle);                       // #4
  EXPECT_TRUE(painter.DrawCircle(0, 0, 10));      // #5
}

如果DrawCircle没有调用PenDown()的话,你会非常失望地得到测试失败的信息。

path/to/my_test.cc:119: Failure
Actual function call count doesn't match this expectation:
Actually: never called;
Expected: called at least once.
Stack trace:
...

EXPECT_CALL,第一个参数是mock对象,比如上面的turtle,然后使用gmock串行调用行为函数。

EXPECT_CALL(mock_object, method(matchers))
    .Times(cardinality) // 执行次数
    .WillOnce(action) // 执行一次
    .WillRepeatedly(action); // 重复执行


out-of-box play

我们假设Foo类中有两个方法:

  • 有一个方法 Foo::count() ,它还未实现。
  • 有一个方法 Foo::picky() 它非常地“挑剔”,只会计算小于50的输入,否则返回-1;
//foo.h
#ifndef _FOO_H_
#define _FOO_H_
class Foo {
public:
  virtual int count(int x) = 0; // 未实现
  int picky(int x) {
     int ret = count(x);
     if(ret < 50) {
        return ret;
     return -1;
#endif

我们想测试picky,但是因为count没实现,这里就是gmock大显身手的时候了:

我们定义一个模拟类 MockFoo 来继承Foo,并且调用一个 MOCK_METHOD

MOCK_METHOD的一般形式为:

MOCK_METHOD(ReturnType, MethodName, (Args...));
MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));

然后我们写一个模拟count的方法:

class MockFoo : public Foo {
 public:
  MOCK_METHOD(int, count,(int));
};

前三项都是非常清晰的。Specs是修饰符,常用的有 const overide noexcept 等。

之后就是大名鼎鼎的 EXPECT_CALL 了,当然如果你希望测试遇到问题就停止的话,可以选择她的姊妹 ASSERT_CALL 。它的使用方法如下

EXPECT_CALL(mock_object, method(matcher))
.Times(cardinality)  // 期望调用cardinality次
.WillOnce(action)  // 执行一次
.WillRepeatedly(action);  // 执行多次

第一行中mock_object是模拟对象,通常采用 MockFoo mock_foo 定义。method的期望调用的方法。 (matcher) 是可有可无的, 只有存在函数同名重载的情况下 (matcher) 才是必须的,用于匹配入参格式。如果为空,表示匹配任意参数,相当于 ::testing::_

假设我们期望调用count三次,第一次返回3,第二次返回60,之后一直返回40;可以这样写

  EXPECT_CALL(mockFoo, count())
    .Times(3)
    .WillOnce(Return(3))
    .WillOnce(Return(60))
    .WillRepeatedly(Return(40));

总结 测试函数 main.cc

//main.cc
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "foo.h"
using ::testing::Return;
using ::testing::_;
class MockFoo : public Foo {
 public:
  MOCK_METHOD1(count, int(int x));
TEST(FooTest, foo) {
  MockFoo mockFoo;
  EXPECT_CALL(mockFoo, count(_))
    .Times(4)
    .WillOnce(Return(3))
    .WillOnce(Return(60))
    .WillRepeatedly(Return(40));
  EXPECT_EQ(mockFoo.picky(3), 3);
  EXPECT_EQ(mockFoo.picky(60), -1);
  EXPECT_EQ(mockFoo.picky(40), 40);
  EXPECT_EQ(mockFoo.picky(40), 40);
int main(int argc, char **argv)
 testing::InitGoogleTest(&argc, argv);
 return RUN_ALL_TESTS();
}

其中EXPECT_CALL是用于模拟函数行为的关键函数。WillOnce表示运进行一次调用所产生的行为。main函数是 固定且必须的。

  • 在命令行运行
g++ -o main main.cc -lgtest -lgmock -lpthread -std=c++11
./main
测试结果

———————————————強者の境界線——————————————————

高级篇

MOCK类一般形式如下:

class MyMock {
 public:
  MOCK_METHOD(ReturnType, MethodName, (Args...));
  MOCK_METHOD(ReturnType, MethodName, (Args...), (Specs...));
};

对于复杂的ReturnType,官方建议是打括号或者类型别名:

class MyMock {
 public:
  // The following 2 lines will not compile due to commas in the arguments:
  MOCK_METHOD(std::pair<bool, int>, GetPair, ());              // Error!
  MOCK_METHOD(bool, CheckMap, (std::map<int, double>, bool));  // Error!
  // One solution - wrap arguments that contain commas in parentheses:
  MOCK_METHOD((std::pair<bool, int>), GetPair, ());
  MOCK_METHOD(bool, CheckMap, ((std::map<int, double>), bool));
  // Another solution - use type aliases:
  using BoolAndInt = std::pair<bool, int>;
  MOCK_METHOD(BoolAndInt, GetPair, ());
  using MapIntDouble = std::map<int, double>;
  MOCK_METHOD(bool, CheckMap, (MapIntDouble, bool));
};

EXPECT_CALL

EXPECT_CALL一般形式如下:

EXPECT_CALL(mock_object,method_name(matchers...))

顺序

对于不同的链式语句需要注意使用的顺序:

EXPECT_CALL(mock_object, method_name(matchers...))
    .With(multi_argument_matcher)  // Can be used at most once
    .Times(cardinality)            // Can be used at most once
    .InSequence(sequences...)      // Can be used any number of times
    .After(expectations...)        // Can be used any number of times
    .WillOnce(action)              // Can be used any number of times
    .WillRepeatedly(action)        // Can be used at most once
    .RetiresOnSaturation();        // Can be used at most once

With-》Times-》InSequence-》After-》WillOnce-》WillRepeatedly-》RetiresOnSaturation

With

.With( multi_argument_matcher )

按照 multi_argument_matcher 的限制进行调用。比如:

using ::testing::_;
using ::testing::Lt;
EXPECT_CALL(my_mock, SetPosition(_, _))
    .With(Lt());

希望第一个参数小于第二个参数。With最多调用一次,且必须为第一句话。


Times

.Times( cardinality )

指定mock函数调用多少次。

基数 描述
AnyNumber() 调用任意次数
AtLeast(n) 最少调用n次
AtMost(n) 最多调用n次
Between(m, n) 调用[m, n]次
Exactly(n) 恰好调用n次

如果没有提供 Times ,那么google test就会进行推测:

  • 如果既没有 WillOnce 指定也没有 WillRepeatedly ,则推断的基数为 Times(1)
  • 如果有 n 个 WillOnce 子句且没有 WillRepeatedly 子句,其中 n >= 1,则推断的基数为 Times(n)
  • 如果有 n 个 WillOnce 子句和一个 WillRepeatedly 子句,其中 n >= 0,则推断的基数为 Times(AtLeast(n))

InSequence

.InSequence( sequences... )

指定预期以特定顺序调用模拟函数。

using ::testing::Sequence;
Sequence s1, s2;
EXPECT_CALL(my_mock, Reset())
    .InSequence(s1, s2);
EXPECT_CALL(my_mock, GetSize())
    .InSequence(s1);
EXPECT_CALL(my_mock, Describe())