GoogleTest之创建Mock

目录

  • MOCK_METHOD
  • mock方法的访问属性
  • mock非虚函数
  • mock自由函数
  • Nice/Strict/Naggy
  • mock方法简化参数
  • mock具体类的替代方法
  • 代理给fake类或某个对象
  • mock方法默认使用父类函数

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一个自由函数(不是类的成员函数都叫自由函数),但是可以通过以下方式实现:

  1. 实现一个接口类,在继承这个接口类的函数中调用这个自由函数以实现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);
  }
};
  1. 通过mock std::function实现。
  • ::testing::MockFunction有两个mock方法:
    • R Call(T1, ..., Tn)
    • std::function 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但是被调用了。这种被称为不感兴趣的调用,会有告警信息。如下:
GoogleTest之创建Mock_第1张图片
如果要忽略这种信息,可以使用NiceMock代替mock_foo
NiceMock是MockFoo的一个子类。以下方式调用完之后没有uninsterestring信息。

using ::testing::NiceMock;
TEST(test_override, mock_override02) {
    NiceMock<MockFoo> foo;   // 使用NiceMock代替MockFoo
    EXPECT_CALL(foo, Add(::testing::_))
    .Times(1)
    .WillRepeatedly(testing::Return(12));

    EXPECT_EQ(foo.DoThis(), 12);
}

StrictMock 的用法和NiceMock一样,期望所有的不感兴趣的调用失败。(看输出结果和NiceMock基本一样,不知道有啥区别?)
注意事项:

  • NiceMock and StrictMock只作用于使用MOCK_METHOD 宏定义的mock函数中,如果mock函数定义在MockFoo的base类中,NiceMock或StrictMock不会影响它。
  • 如果MockFoo 类的析构是非虚函数,NiceMockStrictMock可能不会正常工作

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具体类的替代方法

  • 通过给具体类定义一个父接口类的方式

代理给fake类或某个对象

如果要将一个类的mock函数实现转交给其他类(fake类或其他对象),可以通过以下示例方式

// 定义原类
class Foo {
public:
  virtual ~Foo() {}
  virtual char DoThis(int n) = 0;
  virtual void DoThat(const char* s, int * p) = 0;
};
// Fake为fake类,或者其他从Foo派生下来的类
class FakeFoo : public Foo {
public:
  char DoThis(int n) override {
      return (n > 0) ?  '+' : (n < 0) ? '-': '0';
  }

  void DoThat(const char* s, int * p) override {
      *p = strlen(s);
  }
};

在Mock类中使用其他类(如FakeFoo)代理实现

class MockFoo : public Foo {
public:
  MOCK_METHOD(char, DoThis, (int n), (override));
  MOCK_METHOD(void, DoThat, (const char* s, int * p), (override));

  void DelegateToFake() {
      ON_CALL(*this, DoThis).WillByDefault([this] (int n) {
          return fake_.DoThis(n); 
      });
      ON_CALL(*this, DoThat).WillByDefault([this] (const char* s, int* p) {
          fake_.DoThat(s, p);
      });
  };

private:
  FakeFoo fake_;
};

TEST函数中可以直接使用,不需要指定action

TEST(test_mock_delegatetofake, mock_delegatetofake01) {
    delegate_fake::MockFoo foo;
    foo.DelegateToFake();

    EXPECT_CALL(foo, DoThis(5));
    EXPECT_CALL(foo, DoThat(_, _));

    int n = 0;
    EXPECT_EQ('+', foo.DoThis(5));
    foo.DoThat("Hi", &n);
    EXPECT_EQ(2, n);
}

mock方法默认使用父类函数

class PFoo {
 public:
  virtual ~PFoo() {}

  virtual void Pure(int n) = 0;
  virtual int Concrete(const char* str) { return 1; }
};

这个类中Concrete不是纯虚函数,如果直接想使用该函数作为mock函数

class PMockFoo : public PFoo {
 public:
  // Mocking a pure method.
  MOCK_METHOD(void, Pure, (int n), (override));
  // Mocking a concrete method.  Foo::Concrete() is shadowed.
  MOCK_METHOD(int, Concrete, (const char* str), (override));
};
  1. 可以在action中直接使用父类PFoo的函数:
TEST(test_mock_delegatetofake, mock_delegatetoparent) {
  delegate_fake::PMockFoo foo;
  EXPECT_CALL(foo, Concrete).WillOnce([&foo](const char* str) {
    return foo.PFoo::Concrete(str);
  });
  EXPECT_EQ(foo.Concrete("hello"), 2);
}
  1. 可以告诉mock对象不要mock该函数(TODO)
ON_CALL(foo, Concrete).WillByDefault([&foo](const char* str) {
    return foo.Foo::Concrete(str);
  });

你可能感兴趣的:(GoogleTest,c++,单元测试,测试工具,模块测试)