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

2023-11-02

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

在本系列前序的文章中已经介绍了CMake很多内容,本文是针对单元测试的外延。本系列更多精彩文章敬请关注公众号【很酷的程序员】的话题:CMake

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

  1. 何为单元测试

  2. 何为gtest

  3. 怎么使用gtest

  4. 怎么运行测试

本文仍以开源项目:https://gitee.com/RealCoolEngineer/cmake-template为例,后续示例代码基于上一篇文章的状态进行修改,本文对应的commit id为:c9f1c21

一 单元测试是什么?

单元测试(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_EQgtest中的断言分成两大类:

  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为测试目标添加依赖gtestgtest_main

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

需要注意的不同就是,依旧将单元测试的源文件编译为可执行文件,并且链接的时候链接了gtestgtest_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)更快找到错误的输出(如果有失败的测试)。

上面即为在CMake项目中引入gtest框架的示例,关于gtest更多的信息可以阅读gtest的官方文档:

  1. GoogleTest Primer

  2. GoogleTest User's Guide

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CMake应用:集成gtest进行单元测试 的相关文章

随机推荐

  • Parcel打包React

    Parcel打包React Parcel介绍 Parcel 官网 parceljs org 官网上的介绍 极速零配置Web应用打包工具 什么 对的 你没看错 它标称的零配置打包 这个打包工具其实在一些大厂 开发 Electron 和 Rea
  • PAT C入门题目-7-18 出租车计价 (15 分)

    7 18 出租车计价 15 分 本题要求根据某城市普通出租车收费标准编写程序进行车费计算 具体标准如下 起步里程为3公里 起步费10元 超起步里程后10公里内 每公里2元 超过10公里以上的部分加收50 的回空补贴费 即每公里3元 营运过程
  • MAC安装Securecrt

    文章目录 一 下载地址 二 安装软件 1 下载的文件有2个 一个是安装包 一个是安装文件 2 打开安装包以后 将安装程序拖到应用程序中 三 执行安装文件 1 执行安装 2 错误解决 四 安装软件 1 打开SecureCTR后 选择Enter
  • 关于Swagger中访问不了文档页面

    因为在SpringBoot启动类中 没有加上 EnableSwagger2WebMvc注解 这个注解的作用是启用swagger对应用程序暴露的API端点进行文档化 个人推断和拦截器拦截请求有关 解决办法就是加 EnableSwagger2W
  • 计算机精英ACM fellow和ACM杰出科学家,各校校友统计

    首先谢谢东南 大学网友青山人的统计工作 http bbs netbig com thread 2675926 1 1 html 人数相同 按照学校名称拼音排序 先统计最高荣誉 ACM Fellow 1 的精英 中国科大 81硕 李 凯 19
  • Spring面试问答Top 25

    本人收集了一些在大家在面试时被经常问及的关于Spring的主要问题 这些问题有可能在你下次面试时就会被问到 对于本文中未提及的Spring其他模块 我会单独分享面试的问题和答案 欢迎大家向我推荐你在面试过程中遇到关于Spring的问题 我会
  • APP 性能测试工具

    一 APP 自动化测试工具Appium 官网 http appium io GitHub 地址 https github com appium appium 介绍 Appium 是一个开源的 跨平台的自动化测试工具 支持自动化 iOS An
  • 使用adb 命令(atrace)抓起systrace的方法。【转】

    本文转载自 https www cnblogs com liuliu word p 9963017 html adb shell atrace c b 10240 async start z gfx 1 执行查看adb shell atra
  • 【Python案例】一键自动抠图生成证件照

    0 效果与体验 不想去照相馆 担心肖像隐私被第三方获取 不会抠图 本文实现基于人工智能的一键自动抠图生成证件照 在进入正文之前 先看最终效果 为了让读者快速体验 我写了个小程序 证照工具箱 可打开直接体验 1 人脸检测 在制作证件照时 首选
  • Windows下用pandoc将LaTex转成Word——使用错误总结

    以下是废话阶段 一般期刊投稿都是latex版本啊 奈何有时候机缘总是那么巧合 假如需要word版本呢 科研的乐趣 不就是发现问题 解决问题嘛 那么 就开始愉快地解决问题吧 以下是正片 首先 从无到有的过程当然是先借鉴别人的东西啦 所以 我主
  • idea自动生成类和方法注释

    idea自动生成类和方法注释 一 类注释 方式一 打开settings gt File and COde Templates 选中Files gt Class 添加类注释信息 新建一个类 就会看到类上会自动添加注释 方式二 通过设置文件头来
  • 超5星难度【微软Core allocation】Coding赛题 - 解题思路&优秀代码分享,邀你来“找茬儿”

    6月23日英雄会平台发布了一道难度为超5星的微软比赛题目 截止活动结束共有300多名编程爱好者参与线上答题 而最终通过者仅有7人 通过率仅为2 为什么成绩如此出人意料 是因为原题的英文描述难以理解 还是题目本身的难度太高让很多人望而生畏知难
  • Web前端简易复习手册(一)

    Web前端复习题 一 1 创建js对象的几种方式 2 如何访问对象属性和方法 3 解构是什么 4 什么是原型 原型链 5 基于原型的继承两种方式 6 实例成员 7 原型成员 8 类成员 9 Rest参数含义 10 简述arguments对象
  • 动手学数据分析 Task 1

    动手学数据分析 Task 1 一 数据加载 二 Pandas基础 三 探索性数据分析 一 数据加载 项目源数据请见 kaggle 1 对于csv中的数据 pandas库提供两种方法来读取 分别为 read csv 和read table 二
  • null、undefined、NaN的区别。它们之间的隐式转换和全等结果又是什么?

    前言 在 JavaScript 中 null undefined 和 NaN 是三种不同的特殊值 它们在使用时有着不同的含义和用途 而我们在做项目时候 常常与它们打交道 因此 在编写 JavaScript 代码时需要了解它们之间的区别和相互
  • tensorflow BP神经网络 波士顿房价预测

    前言 啥也别说了 上代码 code 根据波士顿房价信息进行预测 多元线性回归 特征数据归一化 可视化 TensorBoard可视化 读取数据 from sklearn metrics import mean squared error 均方
  • 强化学习:玩转Atari-Pong游戏

    玩转Atari Pong游戏 该项目基于PaddlePaddle框架完成 详情见玩转Atari Pong游戏 Atari 雅达利 最初是一家游戏公司 旗下有超过200款游戏 不过已经破产 在强化学习中 Atari游戏是经典的实验环境之一 因
  • python数字信号_Python数字信号处理应用

    第 1章 声音和信号1 1 1 周期信号1 1 2 频谱分析3 1 3 信号4 1 4 波形的读写6 1 5 频谱7 1 6 波形对象7 1 7 信号对象8 第 1章 声音和信号1 1 1 周期信号1 1 2 频谱分析3 1 3 信号4 1
  • 电脑大小写怎么切换_快速练习好电脑打字的方法

    使用电脑的基本技能 打字 很多人打字都是二指禅 极其不专业 让人看了感觉到非常的搞笑 当有专业的人看见你二指禅打字 就会感觉你这个人水平不行 根本不会盲打 要想熟练地使用电脑 打字是基本的技能 一定要练习会盲打 练习好计算机键盘的盲打 盲打
  • CMake应用:集成gtest进行单元测试

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