编程语言
首页 > 编程语言> > 【C++】GoogleTest进阶之gMock

【C++】GoogleTest进阶之gMock

作者:互联网

gMock是什么

当我们去写测试时,有些测试对象很单纯简单,例如一个函数完全不依赖于其他的对象,那么就只需要验证其输入输出是否符合预期即可。

但是如果测试对象很复杂或者依赖于其他的对象呢?例如一个函数中需要访问数据库或者消息队列,那么要想按照之前的思路去测试就必须创建好数据库和消息队列的客户端实例,然后放在该函数内使用。很多时候这种操作是很麻烦的,此时Mock Object就能帮助我们解决这个问题。一个Mock Object实现与真实对象相同的接口,它可以替代真实对象去使用,而我们要做的就是制定好该Mock Object的行为(调用多少次、参数、返回值等等)

参考文档:
gMock官方文档

安装gMock#

gMock现在与gTest是组合使用的关系,因此在安装gTest时默认就会安装gMock,具体的安装方式见github上的官方说明
https://github.com/google/googletest/tree/main/googletest

使用gMock的基本思路#

gMock快速入门

假设我们在做一个用户的账户系统,一个用户会有一个账户,用户提供接口salary,账户提供接口add和getAccount,在用户的salary内会调用账户的add和getAccount接口
特别注意:此处的账户就是我们要mock的对象,它是用户的一个依赖。要想模拟它,它内部必须有虚析构函数,各个接口也建议是虚函数乃至纯虚函数。这里我的理解是,实际上mock object是对真实对象的代理/替换,在代理模式中比较常见的一种做法就是代理类和被代理类继承自同一个父类/接口

基本样例#

User#


 
Copy
#ifndef USER_H #define USER_H #include <iostream> #include "account.h" class User{ public: /// @brief User类的对象依赖于Account的对象 /// @param account Account实例,被User所依赖 User(Account *account){ account_ = account; } /// @brief 模拟发工资的场景 /// @param money 发的钱数 /// @return 账户余额 int salary(int money){ account_->add(money); return account_->getAccount(); } private: Account *account_; }; #endif //USER_H

Account#


 
Copy
#ifndef ACCOUNT_H #define ACCOUNT_H class Account { public: virtual ~Account() {} virtual void add(int money) = 0; virtual int getAccount() = 0; }; #endif //ACCOUNT_H

mock类编写#

我们要mock的是Account的一个对象,所以书写mock类实现Account接口


 
Copy
#ifndef MOCK_ACCOUNT_H #define MOCK_ACCOUNT_H #include "account.h" #include <gmock/gmock.h> class MockAccount : public Account { public: MOCK_METHOD(void, add, (int money), (override)); MOCK_METHOD(int, getAccount, (), (override)); }; #endif // MOCK_ACCOUNT_H

其中的关键部分在于MOCK_METHOD,很多老的教程中会使用MOCK_METHOD0、MOCK_METHOD1...这些宏,它们分别代表0参数、1参数、2参数的接口。在新的官方教程中没有这种写法,统一都是MOCK_METHOD,内部有四个参数

mock类放在哪#

按照google的建议,除非整个接口就是你自己持有的,否则mock类不要放在xx_test下,因为一旦Account接口被它的所有者改变,MockAccount也必须改变才能继续使用
一般来说,我们不应该mock不是自己持有的接口。如果真的需要mock不是自己持有的,mock对象的目录或者testing的子目录下创建一个.h文件和一个 cc_library with testonly=true,这样一来,每个人都可以使用同一个地方定义的mock类

mock的使用#

创建好mock类之后,要使用它一般分以下几步

样例#

user_test.cc文件


 
Copy
#include <gtest/gtest.h> #include <gmock/gmock.h> #include "user.h" #include "mock_account.h" using ::testing::AtLeast; using ::testing::Return; TEST(UserTest, SalaryIsOK) { MockAccount mAccount;//创建Mock Object EXPECT_CALL(mAccount, add(100)).Times(AtLeast(1)); EXPECT_CALL(mAccount, getAccount()).Times(AtLeast(1));//规范Mock Object的行为,此处是说该mock对象的getAccount()方法至少被调用1次 User user(&mAccount);//将Mock Obejct注入到user中使用(依赖注入) int res = user.salary(100);//测试User业务逻辑 ASSERT_GE(res, 0);//gTest的断言,res大于等于0则通过 }

编译运行

这里我使用CMake来做构建,注意gTest和gMock需要C++14及以上,在链接时直接链接gtest_main,这样就不需要自己写main方法了

CMakeLists.txt#


 
Copy
cmake_minimum_required(VERSION 3.14) project(user LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 14) enable_testing() find_package(GTest REQUIRED) add_executable(test_user "${PROJECT_SOURCE_DIR}/user_test.cc") target_link_libraries(test_user GTest::gtest_main gmock) include(GoogleTest) gtest_discover_tests(test_user)

运行结果


 
Copy
[==========] Running 1 test from 1 test suite. [----------] Global test environment set-up. [----------] 1 test from UserTest [ RUN ] UserTest.SalaryIsOK [ OK ] UserTest.SalaryIsOK (0 ms) [----------] 1 test from UserTest (0 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test suite ran. (0 ms total) [ PASSED ] 1 test.

测试通过了

设置预期行为

使用Mock最核心的点就在于给一个Mock Object规定好预期行为。这部分也是我们需要斟酌的地方。预期行为是设置的严格一点还是松一点全看需求。

标签:c++,icode9,测试,数据库,gMock,纯虚函数,Mock
来源: