Google Test软件集成方法与使用
笔者结合自己的项目实战经验介绍Google Test的集成方法和基本使用教程,可直接将本文中的方法应用到自己的C++软件工程中,本文中的示例代码也可直接编译运行,建议在学习的过程中直接运行示例代码,不会占用你太多时间的。读完本文后,对Google Test会有一个框架型的认知,后续笔者会结合在实际工程中遇到的问题继续对Google Test的高级使用进行讲解。
为什么选择Google Test
目前C++单元测试有gtest
、catch2
、cpputest
等框架,对比其他C++单元测试框架,gtest的使用人数最多,而且资料也比较完善,很多使用方法可以通过Google搜索到。而且gtest被广泛应用在全球很多C++软件项目中(嵌入式领域、航天飞行器领域、自动驾驶领域、金融领域、服务器领域等等),保障了几十亿量级C++代码的可靠性,足矣说明其易用性和可靠性。github排名前三的C++单元测试框架:
Gtest集成方法
单元测试代码是和产品代码紧密相关的,而且单元测试代码要随着产品代码的演变而变化,所以单元测试代码需要和产品代码放在同一个仓库中。可以在产品代码的根目录下,创建UnitTest文件夹,将单元测试代码放到该路径下。目录结构:
|-- 产品代码根目录
|-- UnitTest # 产品代码的单元测试存放在该路径下|-- Fixture # 存放测试夹具代码(下面的教程中会讲解测试夹具)|-- MockInterface # 存放mock代码(下面的教程中会讲解mock)|-- UnitTest.cpp # 单元测试用例代码存放在该文件中|-- CMakeLists.txt # 单元测试代码的cmake编译文件
Gtest使用教程
环境准备
- 安装cmake
- 使用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 Suite
和Test
来命名测试。
一个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
,测试夹具允许用户在不同测试用例中使用相同的数据配置对象。
使用方法:
- 基于
::testing::Test
写一个派生类,在protected中重写SetUp()
和TearDown()
函数,在SetUp()
函数中初始化数据,在TearDown()
中做清理工作 - 测试用例的宏要使用
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软件集成方法与使用相关推荐
- 看看阿里是如何做软件集成发布的
点击蓝色"程序猿DD"关注我哟 来源:云效 作者:董越(花名荷锄),阿里巴巴研发效能部高级产品专家 拼团活动最后一天:跳槽季,我们一起攻克网络协议和算法吧! 当今典型的软件集成发布 ...
- 阿里在使用一种更灵活的软件集成发布模式
当今典型的软件集成发布模式是,通过类似GitHub的Pull Request或GitLab的MergeRequest的方式管理特性分支(Feature Branch):在通过代码评审等方法确认一条特性 ...
- 开源公司黄页之 Google 开源软件推荐
在企业使用开源和贡献开源方面,Google一直是行业的典范.一直以来,Google都在极力推广和倡导开源,并发布了一系列开源项目.如果没有开源软件,Google也难以达到今日的成功.开源中国社区目前收 ...
- Linux软件集成开发环境
package: download from: 软件集成开发环境(代码编辑.浏览.编译.调试) Emacs http://www.gnu.org/software/emacs/ Source-Navi ...
- ubuntu中软件安装方法
ubuntu一些基本软件安装方法 首先说明一下 ubuntu 的软件安装大概有几种方式: 1. deb 包的安装方式 deb 是 debian 系 Linux 的包管理方式, ubuntu 是属于 d ...
- 数据集成方法发展与展望
数据集成方法发展与展望 一. 摘要 二. 发展概要 三. 技术综述 3.1 早期数据集成技术 3.2 后续集成算法的发展 3.3 面向网页表格的数据集成技术 3.4 基于众包的数据集成技术 3.5 数 ...
- 简化软件集成:一个Apache Camel教程
本文来自于阮一峰,文章主要讲解了构建的流程,每个步骤介绍的较为详细,希望对大家有帮助. 软件很少(如果有的话)存在于信息真空中.至少,这是我们软件工程师可以为我们开发的大多数应用程序做出的假设. 在任 ...
- 《企业软件交付:敏捷与高效管理精要》——3.4 企业软件交付的软件工厂方法...
3.4 企业软件交付的软件工厂方法 正如我们前面讨论的,今天的机构面对的商业环境正以前所未有的速度发生变化.与此同时,这些机构还要管理和降低整个机构的运营成本.这就直接意味着,他们不仅要最大限度地减少 ...
- Google 资深软件工程师 LeetCode 刷题笔记首次公开
BAT 等国内的一线名企,在招聘工程师的过程中,对算法和数据结构都会重点考察.但算法易学难精,我的很多粉丝技术能力不错,但面试时总败在算法这一关,拿不到好 Offer.但说实话,数据结构和算法花点时间 ...
最新文章
- ibatis example Class 使用
- 实现快速排序的算法_排序算法-快速排序
- 通过非docker的方式进行RocketMQ的安装
- go语言json字符串解析为结构体数组,结构体指针的数组
- 嵌入式开发中,用C++真香!
- lamp mysql开机自启_centos下设置自启动和配置环境变量的方法
- win7电脑蓝屏没有修复计算机,教你win7开机蓝屏怎么修复
- 年终盘点 | 2019年Java面试题汇总篇(附答案)
- 《社交网站界面设计(原书第2版)》——3.2 注册
- 用漫画让你彻底搞懂 Linux 内核到底长啥样!
- 一招解决二级域名下session失效问题
- centos7下安装pycharm
- Batch size对训练效果的影响
- Hej Stylus for Mac(手写笔画图工具)
- 51单片机------闪烁灯(实验报告)
- 《设计模式之禅》目录
- 奇安信2022面试题
- 中创|又临双11淘宝崩了,中心化存储难以支撑
- p标签内不能包含块级元素
- 听歌学日语2 五十音图 たなは行