C++单元测试GoogleTest和GoogleMock十分钟快速上手(gtest&gmock)

这篇具有很好参考价值的文章主要介绍了C++单元测试GoogleTest和GoogleMock十分钟快速上手(gtest&gmock)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++单元测试GoogleTest和GoogleMock(gtest&gmock)

环境准备

下载

git clone https://github.com/google/googletest.git
# 或者
wget https://github.com/google/googletest/releases/tag/release-1.11.0

安装

cd googletest
cmake CMakeLists.txt 
make
sudo make install

重要文件

googletest
  • gtest/gtest.h
  • libgtest.a
  • libgtest_main.a

当不想写 main 函数的时候,可以直接引入 libgtest_main.a;

g++ sample.cc -o sample -lgtest -lgtest_main -lpthread 
g++ sample.cc -o sample -lgmock -lgmock_main -lpthread

否则

g++ sample.cc -o sample -lgtest -lpthread
googlemock
  • gmock/gmock.h
  • libgmock.a
  • libgmock_main.a

GoogleTest

一 .断言

gtest中的断言分成两大类:

  1. ASSERT_\*系列:如果检测失败就直接退出当前函数

  2. EXPECT_\*系列:如果检测失败发出提示,并继续往下执行

通常情况应该首选使用EXPECT_,因为ASSERT_在报告完错误后不会进行清理工作,有可能导致内存泄露问题。

gtest有很多类似的宏用来判断数值的关系、判断条件的真假、判断字符串的关系。

条件判断
ASSERT_TRUE(condition);  // 判断条件是否为真
ASSERT_FALSE(condition); // 判断条件是否为假

EXPECT_TRUE(condition);  // 判断条件是否为真
EXPECT_FALSE(condition); // 判断条件是否为假
数值比较
ASSERT_EQ(val1, val2); // 判断是否相等
ASSERT_NE(val1, val2); // 判断是否不相等
ASSERT_LT(val1, val2); // 判断是否小于
ASSERT_LE(val1, val2); // 判断是否小于等于
ASSERT_GT(val1, val2); // 判断是否大于
ASSERT_GE(val1, val2); // 判断是否大于等于

EXPECT_EQ(val1, val2); // 判断是否相等
EXPECT_NE(val1, val2); // 判断是否不相等
EXPECT_LT(val1, val2); // 判断是否小于
EXPECT_LE(val1, val2); // 判断是否小于等于
EXPECT_GT(val1, val2); // 判断是否大于
EXPECT_GE(val1, val2); // 判断是否大于等于
字符串比较
ASSERT_STREQ(str1,str2); // 判断字符串是否相等
ASSERT_STRNE(str1,str2); // 判断字符串是否不相等
ASSERT_STRCASEEQ(str1,str2); // 判断字符串是否相等,忽视大小写
ASSERT_STRCASENE(str1,str2); // 判断字符串是否不相等,忽视大小写

EXPECT_STREQ(str1,str2); // 判断字符串是否相等
EXPECT_STRNE(str1,str2); // 判断字符串是否不相等
EXPECT_STRCASEEQ(str1,str2); // 判断字符串是否相等,忽视大小写
EXPECT_STRCASENE(str1,str2); // 判断字符串是否不相等,忽视大小写
谓词断言

谓词断言能比 EXPECT_TRUE 提供更详细的错误消息;

EXPECT_PRED1(pred,val1);
EXPECT_PRED2(pred,val1,val2);
EXPECT_PRED3(pred,val1,val2,val3);
EXPECT_PRED4(pred,val1,val2,val3,val4);
EXPECT_PRED5(pred,val1,val2,val3,val4,val5);

ASSERT_PRED1(pred,val1);
ASSERT_PRED2(pred,val1,val2);
ASSERT_PRED3(pred,val1,val2,val3);
ASSERT_PRED4(pred,val1,val2,val3,val4);
ASSERT_PRED5(pred,val1,val2,val3,val4,val5);
// Returns true if m and n have no common divisors except 1. 
bool MutuallyPrime(int m, int n) { ... }
...
const int a = 3;
const int b = 4;
const int c = 10;
...
EXPECT_PRED2(MutuallyPrime, a, b);  // Succeeds
EXPECT_PRED2(MutuallyPrime, b, c);  // Fails

能得到错误信息:

MutuallyPrime(b, c) is false, where 
b is 4
c is 10

二 .宏测试

如果自己编写mian函数,那么需要调用testing::InitGoogleTest函数进行初始化然后调用RUN_ALL_TESTS(); 函数执行所有的测试集

TEST

进一步,为了更好的组织test cases,比如针对Factorial函数,输入是负数的cases为一组,输入是0的case为一组,正数cases为一组。gtest提供了一个宏TEST(TestSuiteName, TestName),用于组织不同场景的cases,这个功能在gtest中称为test suite

原型
#define TEST(test_suite_name,test_name)
代码示例

TEST_F()宏的第一个参数(即test_suite_name的名称)必须是测试装置类的类名。

TEST(test_suite_name,test_name)
{
    //可以像普通函数一样定义变量之类的行为。
    EXPECT_TRUE(condition);
    EXPECT_EQ(val1, val2);
    EXPECT_PRED1(pred,val1);
}
TEST_F

我们想让多个Test使用同一套数据配置时,就需要用到测试装置,创建测试装置的具体方法如下:

  • 派生一个继承 ::testing::Test 的类,并将该类中的一些内容声明为 protected 类型,以便在子类中进行访问;
  • 根据实际情况,编写默认的构造函数或SetUp()函数,来为每个 test 准备所需内容;
  • 根据实际情况,编写默认的析构函数或TearDown()函数,来释放SetUp()中分配的资源;
  • 根据实际情况,定义 test 共享的子程序。

TEST_F()宏的第一个参数(即Test Case的名称)必须是测试装置类的类名。

它继承testing::Test类,然后根据我们的需要实现下面这两个虚函数:

  • virtual void SetUp()类似于构造函数,总是在测试用例开始时被调用
  • virtual void TearDown()类似于析构函数,总是在测试用例结束后被调用

此外,testing::Test还提供了两个static函数:

  • static void SetUpTestSuite():在第一个TEST之前运行
  • static void TearDownTestSuite():在最后一个TEST之后运行
代码示例
class QueueTestSmpl3 : public testing::Test { // 继承了 testing::Test
protected:  

  static void SetUpTestSuite() {
    std::cout<<"run before first case..."<<std::endl;
  } 

  static void TearDownTestSuite() {
    std::cout<<"run after last case..."<<std::endl;
  }

  virtual void SetUp() override {
    std::cout<<"enter into SetUp()" <<std::endl;
    q1_.Enqueue(1);
    q2_.Enqueue(2);
    q2_.Enqueue(3);
  }

  virtual void TearDown() override {
    std::cout<<"exit from TearDown" <<std::endl;
  }

  static int Double(int n) {
    return 2*n;
  }

  void MapTester(const Queue<int> * q) {
    const Queue<int> * const new_q = q->Map(Double);

    ASSERT_EQ(q->Size(), new_q->Size());

    for (const QueueNode<int>*n1 = q->Head(), *n2 = new_q->Head();
         n1 != nullptr; n1 = n1->next(), n2 = n2->next()) {
      EXPECT_EQ(2 * n1->element(), n2->element());
    }

    delete new_q;
  }

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};
测试集代码
// in sample3_unittest.cc

// Tests the default c'tor.
TEST_F(QueueTestSmpl3, DefaultConstructor) {
  // !!! 在 TEST_F 中可以使用 QueueTestSmpl3 的成员变量、成员函数 
  EXPECT_EQ(0u, q0_.Size());
}

// Tests Dequeue().
TEST_F(QueueTestSmpl3, Dequeue) {
  int * n = q0_.Dequeue();
  EXPECT_TRUE(n == nullptr);

  n = q1_.Dequeue();
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(1, *n);
  EXPECT_EQ(0u, q1_.Size());
  delete n;

  n = q2_.Dequeue();
  ASSERT_TRUE(n != nullptr);
  EXPECT_EQ(2, *n);
  EXPECT_EQ(1u, q2_.Size());
  delete n;
}

// Tests the Queue::Map() function.
TEST_F(QueueTestSmpl3, Map) {
  MapTester(&q0_);
  MapTester(&q1_);
  MapTester(&q2_);
}
运行结果
% ./sample3_unittest
Running main() from /Users/self_study/Cpp/OpenSource/demo/include/googletest/googletest/samples/sample3_unittest.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from QueueTestSmpl3
run before first case...    # 所有的test case 之前运行
[ RUN      ] QueueTestSmpl3.DefaultConstructor
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.DefaultConstructor (0 ms)
[ RUN      ] QueueTestSmpl3.Dequeue
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.Dequeue (0 ms)
[ RUN      ] QueueTestSmpl3.Map
enter into SetUp()          # 每次都会运行
exit from TearDown
[       OK ] QueueTestSmpl3.Map (0 ms)
run after last case...      # 所有test case结束之后运行
[----------] 3 tests from QueueTestSmpl3 (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.

GoogleMock

当你写一个原型或测试,往往不能完全的依赖真实对象。一个 mock 对象实现与一个真实对象相同的接口,但让你在运行时指定它时,如何使用?它应该做什么?(哪些方法将被调用?什么顺序?多少次?有什么参数?会返回什么?等)

可以模拟检查它自己和调用者之间的交互;

mock 用于创建模拟类和使用它们;

  • 使用一些简单的宏描述你想要模拟的接口,他们将扩展到你的 mock 类的实现;
  • 创建一些模拟对象,并使用直观的语法指定其期望和行为;
  • 练习使用模拟对象的代码。 Google Mock会在出现任何违反期望的情况时立即处理。

注意

googlemock 依赖 googletest;调用 InitGoogleMock 时会自动调用 InitGoogleTest ;

头文件 #include “gmock/gmock.h”

什么时候使用?

  • 测试很慢,依赖于太多的库或使用昂贵的资源;
  • 测试脆弱,使用的一些资源是不可靠的(例如网络);
  • 测试代码如何处理失败(例如,文件校验和错误),但不容易造成;
  • 确保模块以正确的方式与其他模块交互,但是很难观察到交互;因此你希望看到观察行动结束时的副作用;
  • 想模拟出复杂的依赖;

使用方法

我们假设一个支付场景逻辑开发业务。我们开发复杂的业务模块,而团队其他成员开发用户行为模块。他们和我们约定了如下接口

class User {
public:
    User() {};
    ~User() {};
public:
    // 登录
    virtual bool Login(const std::string& username, const std::string& password) = 0;
    // 支付
    virtual bool Pay(int money) = 0;
    // 是否登录
    virtual bool Online() = 0;
};

我们的业务模块要让用户登录,并发起支付行为。于是我们的代码如下

class Biz {
public:
    void SetUser(User* user) {
        _user = user;
    }

    std::string pay(const std::string& username, const std::string& password, int money) {
        std::string ret;
        if (!_user) {
            ret = "pointer is null.";
            return ret;
        }
        
        if (!_user->Online()) {
            ret = "logout status.";
            // 尚未登录,要求登录
            if (!_user->Login(username, password)) {
                // 登录失败
                ret += "login error.";
                return ret;
            } else {
                // 登录成功
                ret += "login success.";
            }
        } else {
            // 已登录
            ret = "login.status";
        }

        if (!_user->Pay(money)) {
            ret += "pay error.";
        } else {
            ret += "pay success.";
        }

        return ret;
    }

private:
    User* _user;
};

第一步我们需要Mock接口类

  • MOCK_METHOD0(FUNC, TYPE);第一个参数填写函数名,第二个参数填写函数类型
  • MOCK_METHOD()后面的数字表示需要几个参数
  • const成员方法使用MOCK_CONST_METHOD系列
class TestUser : public User {
public:
    MOCK_METHOD2(Login, bool(const std::string&, const std::string&));
    MOCK_METHOD1(Pay, bool(int));
    MOCK_METHOD0(Online, bool());
};

第二步,我们就可以设计测试场景了。在设计场景之前,我们先看一些Gmock的方法

//   EXPECT_CALL(mock_object, Method(argument-matchers))
//       .With(multi-argument-matchers)
//       .Times(cardinality)
//       .InSequence(sequences)
//       .After(expectations)
//       .WillOnce(action)
//       .WillRepeatedly(action)
//       .RetiresOnSaturation();
//
// where all clauses are optional, and .InSequence()/.After()/
// .WillOnce() can appear any number of times.
  • EXPECT_CALL声明一个调用期待,就是我们期待这个对象的这个方法按什么样的逻辑去执行。
  • mock_object是我们mock的对象,上例中就是TestUser的一个对象。
  • Method是mock对象中的mock方法,它的参数可以通过argument-matchers规则去匹配。
  • With是多个参数的匹配方式指定。
  • Times表示这个方法可以被执行多少次。如果超过这个次数,则按默认值返回了。
  • InSequence用于指定函数执行的顺序。它是通过同一序列中声明期待的顺序确定的。
  • After方法用于指定某个方法只能在另一个方法之后执行。
  • WillOnce表示执行一次方法时,将执行其参数action的方法。一般我们使用Return方法,用于指定一次调用的输出。
  • WillRepeatedly表示一直调用一个方法时,将执行其参数action的方法。需要注意下它和WillOnce的区别,WillOnce是一次,WillRepeatedly是一直。
  • RetiresOnSaturation用于保证期待调用不会被相同的函数的期待所覆盖。

先举一个例子,我们要求Online在第一调用时返回true,之后都返回false。Login一直返回false。Pay一直返回true。也就是说用户第一次支付前处于在线状态,并可以支付成功。而第二次将因为不处于在线状态,要触发登录行为,而登录行为将失败。我们看下这个逻辑该怎么写

    {
        TestUser test_user;
        EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(true));
        EXPECT_CALL(test_user, Login(_,_)).WillRepeatedly(testing::Return(false));
        EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));

        Biz biz;
        biz.SetUser(&test_user);
        std::string admin_ret = biz.pay("user", "", 1);
        admin_ret = biz.pay("user", "", 1);
    }

第4行的意思是Online在调用一次后返回true,之后的调用返回默认的false。第5行意思是Login操作一直返回false,其中Login的参数是两个下划线(_),它是通配符,就是对任何输入参数都按之后要求执行。第6行意思是Pay操作总是返回true。那么我们在第10行和第11行分别得到如下输出

login status.pay success.
logout status.login error.

可以见得输出符合我们的预期。

​ 我们再看一种场景,这个场景我们使用了函数参数的过滤。比如我们不允许admin的用户通过我们方法登录并支付,则可以这么写

    {
        TestUser test_user;
        EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));
        EXPECT_CALL(test_user, Login("admin",_)).WillRepeatedly(testing::Return(false));

        Biz biz;
        biz.SetUser(&test_user);
        std::string admin_ret = biz.pay("admin", "", 1);
    }

第3行表示,如果Login的第一个参数是admin,则总是返回false。于是07行返回是

logout status.login error.

那么如果不是admin的用户登录,则返回成功,这个案例要怎么写呢?

    {
        TestUser test_user;
        EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));
        EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));
        EXPECT_CALL(test_user, Pay(_)).WillRepeatedly(testing::Return(true));

        Biz biz;
        biz.SetUser(&test_user);
        std::string user_ret = biz.pay("user", "", 1);
    }

03行使用了StrNe的比较函数,即Login的第一个参数不等于admin时,总是返回true。08行的输出是

logout status.login success.pay success.

我们再看一个例子,我们要求非admin用户登录成功后,只能成功支付2次,之后的支付都失败。这个案例可以这么写

    {
        TestUser test_user;
        EXPECT_CALL(test_user, Online()).WillOnce(testing::Return(false));
        EXPECT_CALL(test_user, Login(StrNe("admin"),_)).WillRepeatedly(testing::Return(true));
        EXPECT_CALL(test_user, Pay(_)).Times(5).WillOnce(testing::Return(true)).WillOnce(testing::Return(true)).WillRepeatedly(testing::Return(false));

        Biz biz;
        biz.SetUser(&test_user);
        std::string user_ret = biz.pay("user", "", 1);
        user_ret = biz.pay("user", "", 1);
        user_ret = biz.pay("user", "", 1);
    }

第4行我们使用Times函数,它的参数5表示该函数期待被调用5次,从第6次的调用开始,返回默认值。Times函数后面跟着两个WillOnce,其行为都是返回true。这个可以解读为第一次和第二次调用Pay方法时,返回成功。最后的WillRepeatedly表示之后的对Pay的调用都返回false。我们看下执行的结果

logout status.login success.pay success.
logout status.login success.pay success.
logout status.login success.pay error.

从结果上看,前两次都支付成功了,而第三次失败。符合我们的期待。文章来源地址https://www.toymoban.com/news/detail-742241.html

到了这里,关于C++单元测试GoogleTest和GoogleMock十分钟快速上手(gtest&gmock)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 十分钟掌握Java本地缓存

    —————————— Yesterday is history, tomorrow is a mystery, but today is a gift. That is why it’s called the present. —————————— 缓存是Java开发中经常用到的组件,我们会使用缓存来存储一些 不经常改变 的 热点 数据,提高系统处理效率,其根本原因在于内存和硬盘读写速度的

    2024年02月05日
    浏览(67)
  • Django入门,十分钟学会登录网页

    我们假定你已经阅读了 安装 Django。你能知道 Django 已被安装,且安装的是哪个版本,通过在命令提示行输入命令 cmd黑窗口运行,不懂cmd百度一下 如果这是你第一次使用 Django 的话,你需要一些初始化设置。也就是说,你需要用一些自动生成的代码配置一个 Django project ——

    2024年01月24日
    浏览(70)
  • 十分钟python入门 正则表达式

    正则常见的三种功能,它们分别是:校验数据的有效性、查找符合要求的文本以及对文本进行切割和替换等操作。 所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符 元字符大致分成这几类:表示单个特殊字符的,表示空白符的,表示某个范围的,表示次数的量

    2024年02月13日
    浏览(58)
  • 十分钟掌握 Vim 编辑器核心功能

    👉相信不论是前端还是后台多多少少都需要上到服务器上做一些操作,改改配置文件等,大多数 Linux 服务器默认都安装了 Vim 文本编辑器,因此如果还不会 Vim 的话,可能会被同事“耻笑”。 👉如果本文对你有所帮助,请点个👍 吧。 Vim是什么? Vim 是从 vi 发展出来的一个

    2024年02月16日
    浏览(62)
  • 十分钟读懂Stable Diffusion运行原理

    AIGC 热潮正猛烈地席卷开来,可以说 Stable Diffusion 开源发布把 AI 图像生成提高了全新高度,特别是 ControlNet 和 T2I-Adapter 控制模块的提出进一步提高生成可控性,也在逐渐改变一部分行业的生产模式。惊艳其出色表现,也不禁好奇其背后技术。本文整理了一些学习过程中记录的

    2024年02月09日
    浏览(70)
  • 十分钟玩转3D绘图:WxGL完全手册

    WxGL是一个基于PyOpenGL的跨平台三维数据快速可视化工具包,提供类似Matplotlib风格的应用方式。WxGL也可以集成到wxPython或PyQt6中实现更多的功能和控制。 WxGL提供了一套简洁易用、对用户友好的API,将OpenGL的复杂概念封装起来,使得用户可以更加专注于数据的处理,而无需在3

    2024年01月22日
    浏览(73)
  • 十分钟实现 Android Camera2 相机预览

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 Camera2 API 的包名是 android.hardware.camera2 ,是 Android 5.0 后推出的一套调用摄像头设备的接口

    2024年02月13日
    浏览(73)
  • 十分钟实现 Android Camera2 相机拍照

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 上篇文章 我们使用 Camera2 实现了相机预览的功能,这篇文章我们接着上文,来实现 Cam

    2024年02月11日
    浏览(67)
  • 十分钟实现 Android Camera2 视频录制

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 上两篇文章使用 Camera2 实现了相机预览和拍照的功能,这篇文章我们接着上文,来实现

    2024年02月11日
    浏览(53)
  • Nextcloud 使用教程, 十分钟让自己电脑成为私有云盘

    1、什么是Nextcloud? 假设你想在校内搭建一个私有云盘服务,并且有以下的需求,那么Nextcloud,简称NC是你的不二之选, 没有资金的投入,但是有相应的需求; 需要部署在私有云上;有多客户端要求(iOS、Android、MAC、Windows) 安全可靠,长期更新,有和现有系统(例如LDAP集成

    2024年02月11日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包