一、安装 googletest
单测对程序员而言是提升代码质量最重要、最有效的一个措施,对程序员来说,要想写一个好的程序,那么必定少不了好的单元测试。 googletest(gtest) 是 google 开发出来的一个开源的、跨平台的测试框架,是 C++中最出名的测试框架。
gtest 支持 linux 、 windows 以及 mac 系统,安装它依赖下面几项:
- gtest 源码:gtest 属于开源项目,代码仓库 https://github.com/google/googletest 。
- cmake:gtest 使用 cmake 构建项目,编译需要 cmake 环境,cmake 下载地址。
- 编译器:linux 环境下可使用 g++编译,windows 环境下使用 vs 或者 clion 等工具编译,mac 环境使用 xcode 或 clion 等工具编译。
这里的测试环境为 mac+clion(付费),clion 下载地址,选择 clion 是因为 clion 跨平台。
windows 环境除了配置 clion 编译环境以外,其他步骤和 mac 系统一致。
1.1 环境配置
第一步,先使用 git 克隆代码到本地,注意最好不要放到中文路径了。
1 |
git clone https://github.com/google/googletest.git |
第二步,安装 cmake,不同系统的的安装方式不一样,windows 在上面的页面下载一直下一步安装就行了,其他系统可以直接使用对应平台的包管理工具安装。
1 2 3 4 5 6 |
# mac brew install cmake # centos yum install cmake # ubuntu apt-get install cmake |
第三步,安装 clion,linux 和 mac 环境下安装 clion 和 gcc 环境就可以使用了,windows 配置 clion 编译环境可参考 Window10 上 CLion 极简配置教程。
1.2 编译 gtest 库
配置好环境后,使用 clion 打开代码目录,然后载入代码目录,选择 gtest
项目编译生成:
编译成功后生成 libgtestd.a
文件到 cmake 编译路径的 lib
路径下:
生成的 libgtestd.a
即为 gtest 的库文件,项目中引用这个库文件就能使用 gtest 了。
二、使用 googletest
2.1 引入库
将 libgtestd.a 文件拷贝到代码根路径的 lib 路径下,在 CMakeList.txt 中加上以下内容:
1 2 3 4 5 6 |
# 添加上库文件的路径,注意相对路径 link_directories(lib/) # 添加可执行文件 add_executable(demo demo.cpp) # 链接 gtest 库文件 target_link_libraries(demo libgtestd.a) |
2.2 引入头文件
拷贝 googletest/include 目录下的 gtest 目录到当前目录下,然后在 CMakeList.txt 中添加上对应的调用:
1 2 3 |
include_directories( include/ ) |
然后在代码中添加头文件 gtest/gtest.h 就可以使用了。
2.3 测试
添加代码 add.cpp
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include "gtest/gtest.h" int add(int a, int b) { return a + b; } TEST(add, zero) { EXPECT_EQ(0, add(0, 0)); } TEST(add, positive_number) { EXPECT_EQ(3, add(1, 2)); } TEST(add, negative_number) { EXPECT_EQ(-3, add(-1, -2)); } int main() { ::testing::InitGoogleTest(); return RUN_ALL_TESTS(); } |
执行结果:

三、 gtest 的使用教程
参考文档:Googletest Primer,google 官方出品。
3.1 基本用法
gtest 最基本的用法就是断言,它内部提供了很多种断言方式,例如:
1 2 3 4 5 |
ASSERT_EQ() ASSERT_TURE() EXPECT_EQ() EXPECT_TRUE() // ... |
其中 ASSERT_*
的断言,在条件不满足后会终止,而 EXPECT_*
不会终止。
以上面的代码为例,代码编写了一个 add
函数,返回两个传参的和:
1 2 3 |
int add(int a, int b) { return a + b; } |
然后引入 gtest
并写了三个测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include "gtest/gtest.h" TEST(add, zero) { EXPECT_EQ(0, add(0, 0)); } TEST(add, positive_number) { EXPECT_EQ(3, add(1, 2)); } TEST(add, negative_number) { EXPECT_EQ(-3, add(-1, -2)); } |
三个用例分别表示:
- 测试零值相加
- 测试正数相加
- 测试负数相加
主函数中添加启动 gtest 的入口:
1 2 |
::testing::InitGoogleTest(); RUN_ALL_TESTS(); |
运行程序,系统就会自动调用三个测试用例的函数来测试,并输出测试报告:
1 2 3 4 5 6 7 8 |
... [ RUN ] add.zero [ OK ] add.zero (0 ms) [ RUN ] add.positive_number [ OK ] add.positive_number (0 ms) [ RUN ] add.negative_number [ OK ] add.negative_number (0 ms) ... |
如果中间有断言失败的地方,报告也会表达出来。例如修改上面测试中的负数相加函数来制造错误场景:
1 2 3 |
TEST(add, negative_number) { EXPECT_EQ(-3, add(-1, 2)); // 把-2 改成 2,制造错误场景。 } |
再执行测试,报告中就会把不通过的案例展示出来,并且会定位到对应的行,打印出失败的详细原因:
当案例执行失败后,我们也可以打印出一些我们自己的数据以供调试使用,例如:
1 2 3 |
TEST(add, negative_number) { EXPECT_EQ(-3, add(-1, 2)) << "this is a incorrect test"; } |
案例失败后,不仅会打印出失败原因,还会打印出我们自己添加的语句:
3.2 常用断言
基本断言
致命断言 | 非致命断言 | 验证 |
---|---|---|
ASSERT_TRUE(condition); |
EXPECT_TRUE(condition); |
条件 condition 为真 |
ASSERT_FALSE(condition); |
EXPECT_FALSE(condition); |
条件 condition 为假 |
二进制比较
致命断言 | 非致命断言 | 验证 |
---|---|---|
ASSERT_EQ(val1, val2); |
EXPECT_EQ(val1, val2); |
val1 == val2 |
ASSERT_NE(val1, val2); |
EXPECT_NE(val1, val2); |
val1 != val2 |
ASSERT_LT(val1, val2); |
EXPECT_LT(val1, val2); |
val1 < val2 |
ASSERT_LE(val1, val2); |
EXPECT_LE(val1, val2); |
val1 <= val2 |
ASSERT_GT(val1, val2); |
EXPECT_GT(val1, val2); |
val1 > val2 |
ASSERT_GE(val1, val2); |
EXPECT_GE(val1, val2); |
val1 >= val2 |
字符串比较
致命断言 | 非致命断言 | 验证 |
---|---|---|
ASSERT_STREQ(str1,str2); |
EXPECT_STREQ(str1,str2); |
两个 c 字符串内容相同 |
ASSERT_STRNE(str1,str2); |
EXPECT_STRNE(str1,str2); |
两个 c 字符串内容不同 |
ASSERT_STRCASEEQ(str1,str2); |
EXPECT_STRCASEEQ(str1,str2); |
两个 c 字符串内容相同 (忽略大小写) |
ASSERT_STRCASENE(str1,str2); |
EXPECT_STRCASENE(str1,str2); |
两个 c 字符串内容不同 (忽略大小写) |
四、使用 Test Fixtures
Test Fixtures 使用场景:测试案例需要初始化数据或者多个测试案例使用相同的测试数据。
例如在对一个栈的做单元测试时,测试 pop 功能,按照上面的测试方法,测试案例得这么写:
1 2 3 4 5 6 7 8 |
TEST(stack, pop) { my_stack s; // 先推入 3 个元素 s.push(1); s.push(2); s.push(3); // 测试 pop s.pop(); EXPECT_EQ(s.size(), 2); } |
测试过程可以描述为:
- 创建一个栈对象的实例 s 。
- 推入 3 个元素,以便后面 pop 使用。
- 开始测试 pop 。
从直观上来看,所有和 pop 相关的测试案例都要这么来写,要先推入元素,再弹出。而实际上,步骤 1 和步骤 2 是和本轮测试无关,它只起到了初始化数据的作用,它是多余的,但是所有的测试案例又不得不做这一步操作。那么有没有办法解决这个问题呢?有!Test Fixtures 的就是解决这种问题的,它可以在测试案例开始前自动生成好需要的数据。
定义了一个简单的栈的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class my_stack { public: void push(int a) { s.push(a); } void pop() { s.pop(); } int size() { return s.size(); } private: stack<int> s; }; |
再定义一个测试类 stack_test
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class stack_test : public ::testing::Test { protected: void SetUp() override { stack1.push(1); stack1.push(2); } void TearDown() override { } my_stack stack1; my_stack stack2; }; |
它要公有继承于::testing::Test
,其中的 SetUp
和 TearDown
函数分别是初始化和清理函数,也就是类生成前和使用后要做的工作。
此时使用 TEST_F
宏定义来测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
TEST_F(stack_test, push) { EXPECT_EQ(stack1.size(), 2); EXPECT_EQ(stack2.size(), 0); stack2.push(1); EXPECT_EQ(stack2.size(), 1); } TEST_F(stack_test, pop) { EXPECT_EQ(stack1.size(), 2); EXPECT_EQ(stack2.size(), 0); stack1.pop(); EXPECT_EQ(stack1.size(), 1); } |
在执行 TEST_F 之前,gtest 会自动构建一个 stack_test 的实例,并执行 SetUp 函数。也就是说,当真正执行到我们的测试代码的时候,就已经存在一个初始化好的测试环境了。这个时候可以直接访问 stack_test 的内部成员,通过成员变量来做单元测试。
原理来看其实很简单,就是把初始化的过程交给了 gtest 来完成,它来帮我们实例对象,进行初始化,我们直接用就行。
测试结果:
五、其他
5.1 clion 环境跨平台使用 gtest
如何在不改变 CMakeList.txt 的情况下跨平台使用 gtest?配置 CMakeList,根据不同平台读取不同的库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 根据不同平台设置库的目录 if (UNIX AND NOT APPLE) # unix 非苹果系统 set(PROJ_LIBRARY_PATH /home/maqian/code/lib) set(GTEST_LIBRARY_NAME libgtestd.a) elseif (WIN32) # windwos 系统 set(PROJ_LIBRARY_PATH d:/mingw64/lib) set(GTEST_LIBRARY_NAME libgtestd.a) elseif (APPLE) # mac 系统 set(PROJ_LIBRARY_PATH /Users/maqian/code/lib) set(GTEST_LIBRARY_NAME libgtestd.a) else () # 未知系统 MESSAGE(STATUS "other platform: ${CMAKE_SYSTEM_NAME}") endif () # 引入链接库目录 link_directories(${PROJ_LIBRARY_PATH}) # 链接库到目标 target_link_libraries(xxxx ${GTEST_LIBRARY_NAME}) |
1F
太清晰了,谢谢博主!!