集成gtest进行单元测试之cmake应用

news2025/1/10 21:09:56

编写代码有bug是很正常的,通过编写完备的单元测试,可以及时发现问题,并且在后续的代码改进中持续观测是否引入了新的bug。对于追求质量的程序员,为自己的代码编写全面的单元测试是必备的基础技能,在编写单元测试的时候也能复盘自己的代码设计,是提高代码质量极为有效的手段。

在本系列前序的文章中已经介绍了CMake很多内容,本文是针对单元测试的外延。

本文主要介绍以下几个方面的内容:

1、何为单元测试

2、何为gtest

3、怎么使用gtest

4、怎么运行测试

一 单元测试是什么?

单元测试(Unit Testing),一般指对软件中的最小可测试单元进行检查和验证。最小可测试单元可以是指一个函数、一次调用过程、一个类等,不同的语言可能有不同的测试方法,暂时不必深究。

对于C/C++语言,单元测试一般是针对一个函数而言,单元测试的目的就是检测目标函数在所有可能的输入下,函数的执行过程和输出是否符合预期。可以说,单元测试是颗粒度最小的测试,对于软件开发而言,保证每个小的函数执行正确,才能保证利用这些小模块组合起来的系统能够正常工作。

和测试相关的另外一个重要概念是测试用例(Test Case)。百度百科给的定义是,测试用例是对一项特定的软件产品进行测试任务的描述,体现测试方案、方法、技术和策略,包括测试目标、测试环境、输入数据、测试步骤、预期结果、测试脚本等。

这个定义是比较广泛的,对于单元测试来说,就是测试在不同输入下,目标函数(模块)的预期执行过程和输出(返回值),每个不同的情形可以有一个或多个测试用例。编写测试用例需要尽量覆盖所有输入情况(尤其是边界值、特殊值、异常值)。比如下列函数:

  int fibo(int i) {
    if (i == 1 || i == 2) {
      return 1;
    }
    return fibo(i - 1) + fibo(i - 2);
  }

这个函数是为了实现斐波那契数列,所以输入可以分为几类,就可以覆盖所有情况:

1. 小于等于0的整数

2. 1和2

3. 大于2的整数

对应地,可以设置以下测试用例:

1. 输入0,期望值是0

2. 输入1,期望值是1

3. 输入2,期望值是1

4. 输入3,期望值是2

5. 输入4,期望值是3

可以比较明显地发现,如果输入是小于等于0的整数,这个函数就一直递归下去了。这也是开发过程中需要注意的,代码(功能)的使用者并不一定会遵循常规的思维(斐波那契数列不可能输入负数),开发者只能相信自己的代码,不要对输入有任何假设。

上述test case在cmake-template项目的test/c/test_gtest_demo.cc中有示例

二 gtest简介

Google Test是Google开源的一个跨平台的C++单元测试框架,简称gtest,它提供了非常丰富的测试断言、判断宏,极大方便开发者编写测试用例的流程,也是很多开源项目使用的测试框架。

在前面介绍CMake的测试功能时,每个单元测试都是一个可执行文件,实现了main函数,在CMakeLists.txt中使用add_test命令来添加测试用例:

enable_testing()
  add_executable(test_add test/c/test_add.c)
  add_executable(test_minus test/c/test_minus.c)
  target_link_libraries(test_add math)
  target_link_libraries(test_minus math)
  add_test(NAME test_add COMMAND test_add 10 24 34)
  add_test(NAME test_minus COMMAND test_minus 40 96 -56)

通过使用gtest可以简化这个流程,让开发者可以专注在测试用例的书写上,而不用手动编写大量的main函数,以及一些判断输出是否符合预期的附加代码。

三 集成gtest

1 将gtest源码加入项目

gtest是一个开源的框架,代码位于github仓库:google/googletest,本文介绍直接将gtest加入到项目中,通过CMake编译使用。

首先在项目根目录新建一个third_party目录,下载源码的最新release版本,并解压:

 # mkdir third_party
   # cd third_party
   # wget https://codeload.github.com/goog ... tags/release-1.10.0
   # unzip googletest-release-1.10.0.zip

2 将gtest添加为子模块

修改项目根目录的CMakeLists.txt文件,使用上一篇文章介绍的命令add_subdirectory,在开启单元测试时,添加gtest为子模块,并将对应头文件路径添加进来:

 enable_testing()
  add_subdirectory(third_party/googletest-release-1.10.0)
  include_directories(third_party/googletest-release-1.10.0/googletest/include)

 此时执行命令:

 # cmake -B cmake-build
   # cmake --build cmake-build

可以看到构建目录下多了一个目录cmake-build/third_party/googletest-release-1.10.0,并且gtest编译生成了4个新的库文件(gtest子模块的编译目标,位于目录cmake-build/lib下):

1. libgtest.a

2. libgtest_main.a

3. libgmock.a

4. libgmock_main.a

其中libgtest.a提供单元测试相关的功能,libgtest_main.a提供单元测试的主入口,只有链接该库,测试用例就会编译成可执行文件;两个mock库也是类似的,主要提供数据库交互,网络连接等方面的模拟测试,这不是本文的重点。

此时就可以在链接其他目标时直接使用gtest的这4个编译目标(target)。

3 编写测试用例

接下来直接修改先前的两个测试用例源文件,实现相同的测试功能:

1. test/c/test_add.c

2. test/c/test_minus.c

因为使用的是C++测试框架,所以上述两个源文件修改为.cc后缀。

在源文件中include头文件gtest/gtest.h,使用gtest测试用例定义宏来定义测试用例:

TEST(test_case_name, test_name) {}

一个test_case_name下面可以包含多个不同(test_name)的测试。

test/c/test_add.cc内容为:

#include "gtest/gtest.h"
  #include "math/add.h"
  TEST(TestAddInt, test_add_int_1) {
    int res = add_int(10, 24);
    EXPECT_EQ(res, 34);
  }

test/c/test_minus.cc内容为:

#include "gtest/gtest.h"
  #include "math/minus.h"
  TEST(TestMinusInt, test_minus_int_1) {
    int res = minus_int(40, 96);
    EXPECT_EQ(res, -56);
  }

显而易见,测试用例的代码量比之前少了很多,而且更加可读,更加专业。

这里使用了一个判断值相等的断言EXPECT_EQ,gtest中的断言分成两大类:

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

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

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

ASSERT_TRUE(condition);  // 判断条件是否为真
  ASSERT_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); // 判断是否大于等于

对于字符串比较可以使用:

ASSERT_STREQ(str1,str2); // 判断字符串是否相等
  ASSERT_STRNE(str1,str2); // 判断字符串是否不相等
  ASSERT_STRCASEEQ(str1,str2); // 判断字符串是否相等,忽视大小写
  ASSERT_STRCASENE(str1,str2); // 判断字符串是否不相等,忽视大小写

4 添加测试用例

书写好测试用例源文件后,需要修改项目根目录的CMakeLists.txt:

enable_testing()
  add_subdirectory(third_party/googletest-release-1.10.0)
  include_directories(third_party/googletest-release-1.10.0/googletest/include)
  set(GTEST_LIB gtest gtest_main)
  add_executable(test_add test/c/test_add.cc)
  add_executable(test_minus test/c/test_minus.cc)
  target_link_libraries(test_add math gtest gtest_main)
  target_link_libraries(test_minus math gtest gtest_main)
  add_test(NAME test_add COMMAND test_add)
  add_test(NAME test_minus COMMAND test_minus)

对于一个单元测试来说,添加的步骤为:

1. 使用add_executable添加测试目标

2. 使用target_link_libraries为测试目标添加依赖gtest和gtest_main

3. 使用add_test添加到项目,以便可以使用ctest命令执行测试

需要注意的不同就是,依旧将单元测试的源文件编译为可执行文件,并且链接的时候链接了gtest和gtest_main。必须要链接gtest_main库,才能给单元测试添加main函数主入口,否则在链接的时候将会报错。

5 运行测试

在前面的文章中已经介绍过了,在构建编译完成后,进入构建目录,使用ctest命令执行测试即可。 笔者常用的命令为:

make test CTEST_OUTPUT_ON_FAILURE=TRUE GTEST_COLOR=TRUE
  # 或者
  GTEST_COLOR=TRUE ctest --output-on-failure

指定--output-on-failure或者设置CTEST_OUTPUT_ON_FAILURE变量为TRUE,让单元测试失败时输出具体信息,而GTEST_COLOR设置为TRUE可以让输出带有颜色,可以在详细输出模式下(-VV)更快找到错误的输出(如果有失败的测试)。

这里的单元测试也只是作为示例,在真实的项目中,单元测试的编写往往更加复杂,而且这也还只是提高的软件鲁棒性中的一环,追求极致还需要更多努力。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/567676.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

二、CNNs网络架构-卷积分离网络架构

《A review of convolutional neural network architectures and their optimizations》论文指出AlexNet的优异性能证明了可以通过增加网络深度提高网络性能。随着网络层数的不断增加,不断增加的计算负担和不显著的性能提升使得更先进的网络架构成为另一个主要的研究…

如何在华为OD机试中获得满分?Java实现【字符串分隔】一文详解!

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Java华为OD机试真题(2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

uniapp沉浸式渐变状态栏

插件地址&#xff1a;沉浸式渐变状态栏 - DCloud 插件市场 标准用法 <v-headerview actionBarColor"#fac90f" titleColor"#ffffff" pageTitle这是标题 ><template v-slot:title><!--如需自定义标题&#xff0c;用这个slot自定义内容。否则…

基于postman测试接口(整套接口测试)

可以解决的问题 几百个接口人工测试接口过于繁杂大多测试无法使用请求结果当参数可以使用随机参数支持swagger信息导入随账号持久化保存数据对集合一键测试自定义可视化结果 开启控制台 单个测试尝试 使用请求结果当参数 pm.test("存全局参数", function () {// 获…

【深度学习】基于Python Qt的口罩检测与报警系统

文章目录 yolov7训练系统集成数据库报警记录查看qt页面跳转方式qt 的数据库某表查看页面如何写q742971636 yolov7训练 yolov7:https://github.com/WongKinYiu/yolov7 人脸口罩数据集&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1bnxJPnoRNwUfVzLxKjIvkQ?pwdc0yc …

Confluence的数据迁移备份与恢复

目录 一、简介 二、数据备份&#xff08;默认系统会自动备份&#xff0c;不需要手动&#xff09; 2、点击左上角的设置按钮&#xff0c;选择“一般配置”选项。 3、选择“备份与还原”选项 4、开始手动备份 三、数据恢复 1、使用管理员账号登录 2、选择“备份与还原”选…

亿发管理信息化系统,助力五金加工企业信息化建设

传统生产模式下劳动密集的五金生产车间&#xff0c;管理难度逐渐增大&#xff0c;五进生产加工商需要寻求新的竞争力。信息化建设可以将信息技术和系统融入五金生产加工过程的各个环节&#xff0c;使五金生产行业受益。 1、定制生产 信息化建设使五金管理者能够高效地实施定制…

laravel 图表Apexchart 整数多出小数点

Apexchart做统计界面发现一个问题&#xff0c;数据全是整数&#xff0c;但是还显示小数点&#xff0c;如下图&#xff1a; 网上很少有答案&#xff0c;自己研究了很久&#xff0c;分享下经验 第一种方案重新buildDefaultScript方法 protected function buildDefaultScript(){$…

R语言实践——rWCVP 的函数清单

rWCVP 的函数清单 1. get_area_name()用法参数值详介例子 2. get_wgsrpd3_codes()用法参数值详介例子 3. powo_map()用法参数值 4. powo_pal(), scale_color_powo(), scale_colour_powo(), scale_fill_powo()用法参数值 5. redlist_example用法格式资源 6. taxonomic_mapping用…

【万字长文】深度解析 Transformer 和注意力机制(含完整代码实现)

深度解析 Transformer 和注意力机制 在《图解NLP模型发展&#xff1a;从RNN到Transformer》一文中&#xff0c;我介绍了 NLP 模型的发展演化历程&#xff0c;并用直观图解的方式为大家展现了各技术的架构和不足。有读者反馈图解方式虽然直观&#xff0c;但深度不足。考虑到 Tra…

Java设计模式七大原则-单一职责原则

✨作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 单一职责原则 1、单一职责介绍 单一职责原则&#xff08;SRP&#xff1a;Single Responsibility Principle&#xff09;是指一个类…

二层交换机和三层交换机的区别

交换机端口是现代 IT 生态系统中网络通信过程的重要组成部分。通过将数据包从一个网段或网络设备转发到另一个网段或网络设备&#xff0c;它们可实现网络连接和可访问性。但是&#xff0c;并非所有交换机都是平等的。为您的网络选择合适的交换机归结为一个关键决策&#xff1a;…

【Prompt 思考】AI 产品的交互必须是对话和聊天吗?

AI 产品的交互必须是对话和聊天吗&#xff1f; 1. 界面设计2. 对话和聊天3. 提示工程4. 思考5. 工具辅助用户写prompt的界面自动优化promptChatWeb的提示器商店 1. 界面设计 对话没有预设用途&#xff1a;用户不知道它能做什么&#xff0c;其实是什么也能干。提供更好的用户体…

【2023 · CANN训练营第一季】基于昇腾910的TF网络脚本训练(ModelArts平台)

准备工作: 1.注册华为云账号&#xff0c;获取AK/SAK&#xff0c;授权ModelArts&#xff0c;并申请华为云代金券 2.获取训练数据集&#xff0c;并进行数据预处理&#xff0c;比如离线制作成tfrecords(建议&#xff0c;可选) 3.将数据集(训练脚本)上传到OBS 4.安装PycharmIDE及To…

一文细说vDSO机制原理

1. 什么是 vDSO 众所周知&#xff0c;操作系统为我们管理硬件资源&#xff0c;并以系统调用的方式对用户进程提供 API&#xff0c;但是 syscall 很慢&#xff0c;涉及陷入内核以及上下文切换。对于少量频繁调用的系统调用&#xff08;比如获取当期系统时间&#xff09;来说&am…

CDN 回源与CDN 多级缓存原理

一、什么是回源 回源是指浏览器在发送请求报文时&#xff0c;响应该请求报文的是源站点的服务器&#xff0c;而不是各节点上的缓存服务器&#xff08;比如Nginx开启缓存&#xff09;&#xff0c;那么这个过程相对于通过各节点上的缓存服务器来响应的话就称作为回源。回源的请求…

Mac 原神电脑版下载安装使用教程,MacBook 上也可以玩原神了

最近发现了一个很棒的工具&#xff0c;他可以让你的 Mac 苹果电脑运行原神&#xff0c;而且画质和流畅度都是在线的&#xff0c;今天分享给大家 软件名字叫 playCover &#xff0c;根据作者的介绍这款软件最初就是国外的一位博主想在 Mac 上玩原神特意开发的一款软件&#xff…

Faster R-CNN网络架构详解和TensorFlow Hub实现(附源码)

文章目录 一、RPN网络1. RPN网络简介2. backbone网络简介 二、Faster R-CNN网络架构1. Faster R-CNN网络简介2. 基于TensorFlow Hub实现Faster R-CNN 前言&#xff1a;Faster R-CNN的简介见 上一篇文章 一、RPN网络 1. RPN网络简介 RPN网络全称Region Proposal Network&#…

【星戈瑞】BODIPY-530/550氟化硼二吡咯荧光染料

BODIPY是一种荧光染料&#xff0c;其分子结构稳定、荧光强度高、荧光寿命长、光谱范围广&#xff0c;因此在许多领域都有应用。在生物医学领域&#xff0c;BODIPY作为荧光探针&#xff0c;可用于细胞成像、生物分子探测、药物筛选等方面。例如&#xff0c;一些研究者将BODIPY修…

如何在华为OD机试中获得满分?Java实现【计算某字符出现次数】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…