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

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

  在本系列前序的文章中已经介绍了CMake很多内容,本文是针对单元测试的外延。本系列更多精彩文章敬请关注公众号【很酷的程序员】的话题: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/google/googletest/zip/refs/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)更快找到错误的输出(如果有失败的测试)。

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

【整整200集】超超超详细的Python接口自动化测试进阶教程,真实模拟企业项目实战!!

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

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

相关文章

如何使用Flask-Bootstrap和Font Awesome来添加样式和图标到Flask应用程序

当你想要给自己的Flask应用程序添加一些样式和图标,Flask-Bootstrap和Font Awesome是非常棒的工具。它们都是开源的,并且被许多开发人员使用。 首先,你需要将它们添加到你的Flask应用程序中。你可以使用pip命令来安装它们: pip …

SVN限制Message提交的格式

限制Message提交的格式必须为以下格式 [Version] [Description] [TPA] [Doors] REPOS"$1" TXN"$2"# Make sure that the log message contains some text. SVNLOOK/usr/bin/svnlook MSG$SVNLOOK log -t "$TXN" "$REPOS"if [[ $MSG ~ …

【Docker 学习笔记】Docker架构及三要素

文章目录 一、Docker 简介二、Docker 架构1. Docker 客户端和服务器2. Docker 架构图3. Docker 运行流程图 三、Docker 三要素1. 镜像(Image)2. 容器(Container)3. 仓库(Repository) 一、Docker 简介 Dock…

2.4 传统经验光照模型详解

一、光照模型 光照模型(illumination model),也称为明暗模型,用于计算物体某点处的光强(颜色值)。从算法理论基础而言,光照模型分为两类:一种是基于物理理论的,另一种是…

【MATLAB第61期】基于MATLAB的GMM高斯混合模型回归数据预测

【MATLAB第61期】基于MATLAB的GMM高斯混合模型回归数据预测 高斯混合模型GMM广泛应用于数据挖掘、模式识别、机器学习和统计分析。其中,它们的参数通常由最大似然和EM算法确定。关键思想是使用高斯混合模型对数据(包括输入和输出)的联合概率…

<Doc>Windows常见的doc命令

一:管理员身份运行cmd命令: 方式一:搜索框输入cmd,回车,点击:以管理员身份运行 出现如图所示: 方式二:快捷键运行方式: 1.按winr,在运行窗口中输入cmd。 …

JavaSE类和对象(重点:this引用、构造方法)

目录 一、类的定义方式以及实例化 1.面向对象 Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在Java的世界里一切皆为对象。 2.类的定义和使用 1.在java中定义类时需要用到class关键字 3.类的实例化 4.类实例化的使用 二、this引用 …

Open3D点云数据处理(十七):体素滤波之体素中心下采样

文章目录 1 体素下采样原理1.1 体素下采样分类1.2 下采样结果对比2 体素中心下采样代码实现2.1 代码行实现2.2 函数封装 my_voxel_center_down_sample()专栏目录:Open3D点云数据处理(Python) 1 体素下采样原理 点云体素下采样(Voxel Downsampling)是一种将点云数据进行降…

Java API指南:掌握常用工具类与字符串操作

文章目录 1. API简介2. Java API的使用2.1 创建和使用Java API工具类2.2 使用String类进行字符串操作 结语 导语: Java作为一门功能强大的编程语言,其成功之处不仅在于语法结构的简洁明了,更因为其丰富的API(Application Programm…

中药配方煎药-亿发智能中药汤剂煎煮系统,智慧中药房的数字化升级

随着中药的普及,在治病、养生等方面都发挥这积极作用,但中药煎煮过程繁琐,如果有所差错将会影响药品的药性。为了满足当今用户对中药的需求,增强生产效率和业务水平,亿发中药煎配智能管理系统应运而生,为用…

【JMeter】JMeter添加插件

目录 一、前言 二、插件管理器 三、推荐插件 1.Custom Thread Groups (1)Ultmate Thread Group (2)Stepping Thread Group 2.3 Basic Graph 资料获取方法 一、前言 ​ 在我们的工作中,我们可以利用一些插件来帮…

github gitlab 多用户多平台切换

一、背景 我需要用账号1 来登录并管理github 账号 我需要用账号2 来登录并管理gitlab 账号 二、设置账号 邮箱 设置账号1用户名与邮箱 git config --global user.name "miaojiang" git config --global user.email "187133163.com" 三、生成本地密钥…

MyBatis小记_three

目录 注解开发 环境搭建 1.创建Maven工程 2.创建实体类 3.提供接口,并提供增删改查的方法 4.编写SqlMapConfig.xml 主配置文件 5.采用注解的方式,来进行增删改查 6.测试 7.测试保存用户的方法 8.测试更新用户 9.测试删除一个用户 10.根据id查…

中小企业如何低成本实施MES管理系统

中小企业在市场竞争中需要有高效的管理体系来支持其运营和发展。中小企业MES管理系统是一种先进的管理系统,可以提升工厂智能化水平,提高生产效率,是中小企业必须采取的有效管理工具。然而,由于资金和技术的限制,中小企…

【C#常用操作】

excel相关操作 using Excel Microsoft.Office.Interop.Excel;public Excel.Application app; public Excel.Workbooks wbs; public Excel.Workbook wb; public Excel.Worksheets wss; public Excel.Worksheet ws;/// <summary> /// 取得打开excel句柄 /// </summary…

【SpringBoot系列】- 四大核心之actuator(程序监控器)

【SpringBoot系列】- 四大核心之actuator(程序监控器) 文章目录 【SpringBoot系列】- 四大核心之actuator(程序监控器)一、概述二、Spring Boot Actuator应用2.1 在项目POM文件中导入Actuator2.2 application配置2.3 配置详解2.3.1 Sensor 类 endpoints2.3.2 Actuator 类 endpo…

反转链表(JS)

反转链表 题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&…

MySQL初探

Background 通过阅读小林coding&#xff0c;大致了解了mysql数据库的种种特点&#xff0c;与之前学的数据库实现大体思路相同&#xff0c;感觉学习不能停留在理论层面&#xff0c;要调研生产级别的中间件实现。 一条代码运行在mysql上的流程 1. 连接的过程需要先经过 TCP 三次…

php://filter绕过死亡exit

文章目录 php://filter绕过死亡exit前言[EIS 2019]EzPOP绕过exit 参考 php://filter绕过死亡exit 前言 最近写了一道反序列化的题&#xff0c;其中有一个需要通过php://filter去绕过死亡exit()的小trick&#xff0c;这里通过一道题目来讲解 [EIS 2019]EzPOP 题目源码&#…

C语言每日一题:9.《数据结构》链表的中间节点+链表的倒数第k个节点。

第一题&#xff1a; 题目链接&#xff1a; >思路一&#xff1a; 1.第一遍遍历链表&#xff0c;直到找到尾结束第一次遍历&#xff0c;遍历的过程中记录链表长度。定义长度为k。 2.确定中间是第几个节点&#xff0c;计算是k/21根据题目要求。 3.再一次去遍历我们的数组&…