笔者结合自己的项目实战经验介绍Google Test的集成方法和基本使用教程,可直接将本文中的方法应用到自己的C++软件工程中,本文中的示例代码也可直接编译运行,建议在学习的过程中直接运行示例代码,不会占用你太多时间的。读完本文后,对Google Test会有一个框架型的认知,后续笔者会结合在实际工程中遇到的问题继续对Google Test的高级使用进行讲解。

为什么选择Google Test

目前C++单元测试有gtestcatch2cpputest等框架,对比其他C++单元测试框架,gtest的使用人数最多,而且资料也比较完善,很多使用方法可以通过Google搜索到。而且gtest被广泛应用在全球很多C++软件项目中(嵌入式领域、航天飞行器领域、自动驾驶领域、金融领域、服务器领域等等),保障了几十亿量级C++代码的可靠性,足矣说明其易用性和可靠性。github排名前三的C++单元测试框架:

Gtest集成方法

单元测试代码是和产品代码紧密相关的,而且单元测试代码要随着产品代码的演变而变化,所以单元测试代码需要和产品代码放在同一个仓库中。可以在产品代码的根目录下,创建UnitTest文件夹,将单元测试代码放到该路径下。目录结构:

|-- 产品代码根目录
|-- UnitTest          # 产品代码的单元测试存放在该路径下|-- Fixture         # 存放测试夹具代码(下面的教程中会讲解测试夹具)|-- MockInterface   # 存放mock代码(下面的教程中会讲解mock)|-- UnitTest.cpp    # 单元测试用例代码存放在该文件中|-- CMakeLists.txt  # 单元测试代码的cmake编译文件

Gtest使用教程

环境准备

  1. 安装cmake
  2. 使用cmake编译Google Test,在单元测试工程的cmake文件中包含以下操作即可
cmake_minimum_required(VERSION 3.14)
project(my_project)# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)include(FetchContent)
FetchContent_Declare(googletestURL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

这些操作会先从GitHub上将Google Test框架的源码下载到当前的编译路径下,然后和自己的单元测试代码一起编译。
609281088cfefc76f9d0ce82e1ff6c30cc3591e5是Google Test的commit hash,如果有需要,自己可以更新到最新的提交版本。

第一个Google Test测试用例

cmake文件

cmake_minimum_required(VERSION 3.14)
project(my_project)# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)include(FetchContent)
FetchContent_Declare(googletestURL https://github.com/google/googletest/archive/609281088cfefc76f9d0ce82e1ff6c30cc3591e5.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)enable_testing()add_executable(my_testUnitTest.cpp
)
target_link_libraries(my_testgtest_main
)include(GoogleTest)
gtest_discover_tests(my_test)

UnitTest.cpp文件

#include <gtest/gtest.h>// Demonstrate some basic assertions.
TEST(HelloTest, BasicAssertions) {// Expect two strings not to be equal.EXPECT_STRNE("hello", "world");// Expect equality.EXPECT_EQ(7 * 6, 42);
}

直接运行cmake编译,然后运行单元测试:./my_test,除非用户需要自己做一些单元测试前的初始化操作,否则一般情况下不需要用户自己去写main函数,直接使用Google Test提供的main函数即可。

基本工具的介绍

测试命名

Google Test中使用了Test SuiteTest来命名测试。
一个Test Suite可包含多个Test,同一套Test Suite具有相同的Suite命名,不同的Test有各自不同的命名,例:

TEST(TestSuiteName_1, TestName_1) {... test body ...
}TEST(TestSuiteName_1, TestName_2) {... test body ...
}TEST(TestSuiteName_2, TestName_1) {... test body ...
}

断言宏

ASSERT_*类型的断言宏,如果断言失败就直接终止程序的运行。如果在使用该宏前有动态内存分配的操作,该宏可能会导致程序终止运行,继而出现内存泄漏现象的,所以使用该断言宏时,一定注意不要内存泄漏
EXPECT_*类型的断言宏,如果断言失败不会终止程序的运行,而是继续执行下面的代码。一般单元测试中会使用该宏,除非是一些无法接受的程序运行结果或者程序运行结果会导致下面的测试无法进行的情况才会使用ASSERT_*

测试夹具的使用

当多个测试中需要使用相同的数据内容,这种情况下可以使用test fixture,测试夹具允许用户在不同测试用例中使用相同的数据配置对象。
使用方法:

  1. 基于::testing::Test写一个派生类,在protected中重写SetUp()TearDown()函数,在SetUp()函数中初始化数据,在TearDown()中做清理工作
  2. 测试用例的宏要使用TEST_F,该宏是专门为测试夹具的使用准备的,在测试用例开始时自动执行SetUp函数,用例结束时自动执行TearDown函数

TEST_F的使用

TEST_F(TestFixtureName, TestName) {... test body ...
}

代码范例

// 需要测试的代码
template <typename E>  // E is the element type.
class Queue {public:Queue();void Enqueue(const E& element);E* Dequeue();  // Returns NULL if the queue is empty.size_t size() const;...
};// 自己定义派生类QueueTest
class QueueTest : public ::testing::Test {protected:void SetUp() override {q1_.Enqueue(1);q2_.Enqueue(2);q2_.Enqueue(3);}// void TearDown() override {}Queue<int> q0_;Queue<int> q1_;Queue<int> q2_;
};// 使用TEST_F测试代码
TEST_F(QueueTest, IsEmptyInitially) {EXPECT_EQ(q0_.size(), 0);
}TEST_F(QueueTest, DequeueWorks) {int* n = q0_.Dequeue();EXPECT_EQ(n, nullptr);n = q1_.Dequeue();ASSERT_NE(n, nullptr);EXPECT_EQ(*n, 1);EXPECT_EQ(q1_.size(), 0);delete n;n = q2_.Dequeue();ASSERT_NE(n, nullptr);EXPECT_EQ(*n, 2);EXPECT_EQ(q2_.size(), 1);delete n;
}

以上两个测试互不干扰,这两个测试用例使用相同的数据内容,但QueueTest对象是不同的。每个用例执行开始时会创建新的QueueTest对象,用例执行完毕后QueueTest对象自动被销毁

mock教程

在单元测试的过程中,有些地方会调用第三方接口或者使用第三方系统,我们不需要去为第三方的代码做单元测试,所以在做单元测试的过程中不应该依赖第三方接口或系统。
在不依赖第三方代码的情况下,为了能使自己代码的逻辑跑通,就需要对第三方接口或系统进行模拟,Google Test提供了gmock机制用来方便地实现模拟功能。

mock基本使用

可直接运行示例代码,mock的详细文字说明见代码注释

#include <gmock/gmock.h>
#include <gtest/gtest.h>namespace {using ::testing::AtLeast;
using ::testing::Return;
using ::testing::Ge;
using ::testing::_;// 用该抽象接口模拟第三方代码
class FooInterface {public:virtual ~FooInterface() {}virtual void DoThis() = 0;virtual int returnValue() = 0;virtual int inputparam(int p) = 0;virtual int inputparam2(int p, int q) = 0;
};// 该类对第三方代码进行模拟打桩,通过MOCK_METHOD宏给抽象接口打桩
class MockFoo : public FooInterface {public:MOCK_METHOD(void, DoThis, (), (override));MOCK_METHOD(int, returnValue, (), (override));MOCK_METHOD(int, inputparam, (int p), (override));MOCK_METHOD(int, inputparam2, (int p, int q), (override));
};TEST(LeakTest, test01) {MockFoo foo;// EXPECT_CALL:该匹配器用来配合mock功能使用// AtLeast(1):至少调用一次DoThis函数// 如果不指定调用次数,EXPECT_CALL默认调用该接口一次EXPECT_CALL(foo, DoThis()).Times(AtLeast(1));foo.DoThis();foo.DoThis();
}TEST(LeakTest, test02) {MockFoo foo;// Times(5):要调用5次returnValue()// WillOnce(Return(150)):调用一次返回150// WillRepeatedly(Return(200)):后续调用全部返回200// WillOnce(Return(100)).WillOnce(Return(150)).WillRepeatedly(Return(200)):该接口前两次返回100,后面三次返回200EXPECT_CALL(foo, returnValue()).Times(5).WillOnce(Return(100)).WillOnce(Return(150)).WillRepeatedly(Return(200));for (int i = 0; i < 5; i++) {printf("time:%d, value: %d\n", i, foo.returnValue());}
}TEST(LeakTest, test03) {MockFoo foo;// inputparam(100):后续调用过程中,期望该接口的入参为100EXPECT_CALL(foo, inputparam(100));foo.inputparam(100);// inputparam:后面没有跟入参,表示用户不关心入参,入参可以是任意值EXPECT_CALL(foo, inputparam).Times(1).WillOnce(Return(50));foo.inputparam(67);// Ge(70):表示期望入参要大于70EXPECT_CALL(foo, inputparam(Ge(70)));foo.inputparam(80);// inputparam2(50, _):用户只期待第一个入参是50,第二个入参可以是任意值(“_”可以匹配任意值)EXPECT_CALL(foo, inputparam2(50, _));foo.inputparam2(50, 98);
}// 上面的例程都是只使用了单个预期,下面几个例程中会出现多预期
// 注意:google test中的预期是有粘性的
TEST(LeakTest, test04) {MockFoo foo;EXPECT_CALL(foo, inputparam(_));EXPECT_CALL(foo, inputparam(100)).Times(2);/* 预期宏是倒叙执行的,先执行EXPECT_CALL(foo, inputparam(100)).Times(2);,然后再执行EXPECT_CALL(foo, inputparam(_)); */// 这两条语句满足了EXPECT_CALL(foo, inputparam(100)).Times(2);foo.inputparam(100);foo.inputparam(100);// 这条语句满足了EXPECT_CALL(foo, inputparam(_));foo.inputparam(900);/* 如果把foo.inputparam(900);换成foo.inputparam(100);就会失败,因为预期宏是有粘性的,连续出现三个foo.inputparam(100);会触发 EXPECT_CALL(foo, inputparam(100)).Times(2);的失败 */
}TEST(LeakTest, test05) {MockFoo foo;// 如果想让预期宏顺序执行,使用如下操作即可{InSequence seq;EXPECT_CALL(foo, inputparam(_));EXPECT_CALL(foo, inputparam(100)).Times(2);}// 按顺序执行预期宏,第一条语句满足了EXPECT_CALL(foo, inputparam(_));条件foo.inputparam(7800);// 按顺序执行预期宏,后面两条语句满足了EXPECT_CALL(foo, inputparam(100)).Times(2);条件foo.inputparam(100);foo.inputparam(100);
}TEST(LeakTest, test06) {MockFoo foo;EXPECT_CALL(foo, inputparam(_));// 使用RetiresOnSaturation()可以消除预期宏的粘性EXPECT_CALL(foo, inputparam(100)).Times(2).RetiresOnSaturation();/* 因为上面已经消除了EXPECT_CALL(foo, inputparam(100)).Times(2)的粘性,所以在前两条foo.inputparam(100);执行完成后,满足了EXPECT_CALL(foo, inputparam(100)).Times(2)的条件,该宏就会自动结束,第三条foo.inputparam(100)则满足了EXPECT_CALL(foo, inputparam(_))的条件 */foo.inputparam(100);foo.inputparam(100);foo.inputparam(100);
}
}  // namespace

cmake编译文件

cmake_minimum_required(VERSION 3.14)
project(unit_test_demo)# GoogleTest requires at least C++11
set(CMAKE_CXX_STANDARD 11)include(FetchContent)
FetchContent_Declare(googletestURL https://github.com/google/googletest/archive/86add13493e5c881d7e4ba77fb91c1f57752b3a4.zip
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
set(BUILD_GMOCK ON)FetchContent_MakeAvailable(googletest)enable_testing()add_executable(my_testUnitTest.cpp
)
target_link_libraries(my_test#gtest_maingmock_main
)include(GoogleTest)
gtest_discover_tests(my_test)

Google Test软件集成方法与使用相关推荐

  1. 看看阿里是如何做软件集成发布的

    点击蓝色"程序猿DD"关注我哟 来源:云效 作者:董越(花名荷锄),阿里巴巴研发效能部高级产品专家 拼团活动最后一天:跳槽季,我们一起攻克网络协议和算法吧! 当今典型的软件集成发布 ...

  2. 阿里在使用一种更灵活的软件集成发布模式

    当今典型的软件集成发布模式是,通过类似GitHub的Pull Request或GitLab的MergeRequest的方式管理特性分支(Feature Branch):在通过代码评审等方法确认一条特性 ...

  3. 开源公司黄页之 Google 开源软件推荐

    在企业使用开源和贡献开源方面,Google一直是行业的典范.一直以来,Google都在极力推广和倡导开源,并发布了一系列开源项目.如果没有开源软件,Google也难以达到今日的成功.开源中国社区目前收 ...

  4. Linux软件集成开发环境

    package: download from: 软件集成开发环境(代码编辑.浏览.编译.调试) Emacs http://www.gnu.org/software/emacs/ Source-Navi ...

  5. ubuntu中软件安装方法

    ubuntu一些基本软件安装方法 首先说明一下 ubuntu 的软件安装大概有几种方式: 1. deb 包的安装方式 deb 是 debian 系 Linux 的包管理方式, ubuntu 是属于 d ...

  6. 数据集成方法发展与展望

    数据集成方法发展与展望 一. 摘要 二. 发展概要 三. 技术综述 3.1 早期数据集成技术 3.2 后续集成算法的发展 3.3 面向网页表格的数据集成技术 3.4 基于众包的数据集成技术 3.5 数 ...

  7. 简化软件集成:一个Apache Camel教程

    本文来自于阮一峰,文章主要讲解了构建的流程,每个步骤介绍的较为详细,希望对大家有帮助. 软件很少(如果有的话)存在于信息真空中.至少,这是我们软件工程师可以为我们开发的大多数应用程序做出的假设. 在任 ...

  8. 《企业软件交付:敏捷与高效管理精要》——3.4 企业软件交付的软件工厂方法...

    3.4 企业软件交付的软件工厂方法 正如我们前面讨论的,今天的机构面对的商业环境正以前所未有的速度发生变化.与此同时,这些机构还要管理和降低整个机构的运营成本.这就直接意味着,他们不仅要最大限度地减少 ...

  9. Google 资深软件工程师 LeetCode 刷题笔记首次公开

    BAT 等国内的一线名企,在招聘工程师的过程中,对算法和数据结构都会重点考察.但算法易学难精,我的很多粉丝技术能力不错,但面试时总败在算法这一关,拿不到好 Offer.但说实话,数据结构和算法花点时间 ...

最新文章

  1. 中国倒数第五!毕马威全球自动驾驶报告|附下载
  2. 惊了!哆啦A梦里最能打的道具,居然真实存在!还打破了世界纪录,看完跪了....
  3. github注册账号一直验证失败
  4. android 京东白条支付,京东网银钱包安卓版上线:整合京东白条和小金库
  5. 【jQuery笔记Part4】01-jQuery-节点操作-添加节点-删除节点-复制节点
  6. 离京2小时,快手百亿入云,一切为了「看见」
  7. java volatile i_为啥Java里面volatile 修饰的i++还是线程不安全的
  8. 不同系统的Single Sign On(单点登录)
  9. 用MDT 2012为企业部署windows 7(三)--安装MDT 2012,ADK以及安装后情况
  10. matplotlib库使用
  11. MongoDB干货系列1-定期巡检之Mtools
  12. [1] 图像预处理----图像灰度化处理
  13. 1546: 回形取数
  14. 社交网络营销之传统营销VS社交网络营销
  15. PyTorch搭建AlexNet模型(在CIFAR10数据集上准确率达到了85%)
  16. Linux使用lrzsz快速上传和下载
  17. matlab 盲源信号分离,基于盲源分离的图像噪声滤除的研究(附Matlab仿真程序)☆
  18. tplink路由器dns服务器未响应,联通光纤猫连接无线路由器设置教程图解
  19. 卸载一个游戏计算机里还有文件,如何处理pc游戏卸载后留下的残余文件?
  20. ​李宏毅机器学习——领域适应Domain Adaptation

热门文章

  1. EXCEL之在单元格加前缀加后缀
  2. 01_心理咨询_微信小程序项目实战_项目概述
  3. ARCOCAD 高级编程(DMIS代码)
  4. 2017最新鑫众游戏大厅源码架设和全套手机版运营级别源码下载
  5. 如何通过四柱反推生辰(出生日期)算法讲解
  6. Error 193:%1 不是合法的Win32 应用程序 查看程序是x86还是x64
  7. 拉格朗日四平方和定理c语言,费马平方和定理 拉格朗日四平方和定理
  8. 解决: Eclipse 提示内存不足
  9. 运行Eclipse提示No java virtual machine was found after searching the follwing locations
  10. WINDOWS下读取EXT2/3磁盘分区工具:ext2fsd