CMake项目使用ctest+gtest进行单元测试

2023-11-04

随着CMake工具越来越强大便捷,越来越多的C/C++项目转而使用CMake来进行编译管理,它还提供了用于测试的ctest命令来执行项目中编写的单元测试。

本文就以一个实例来介绍如何使用ctest来进行单元测试。

一、环境准备

本文实例环境VSCode+MinGW64+CMake+gtest。

需要在MinGW中安装gtest,如果没有安装也没有关系,在CMakeLists.txt中进行检测,如果发现没有安装,则自动下载源码进行编译。

二、新建项目

新建一个目录,比如demo,然后使用VSCode打开目录,创建一个CMake项目。
创建CMake项目可以使用VSCode的CMake向导来创建,也可以直接在目录中编写一个CMakeLists.txt来创建。

使用VSCode的CMake向导创建项目

在VSCode中按F1,在弹出的选项中选择CMake:快速入门

在这里插入图片描述

然后选择编译套件,如果需要搜索,可以选择[Scan for kits]

在这里插入图片描述

然后输入项目名称,比如demo

在这里插入图片描述

根据项目需要选择库(Library)或者可执行体(Executable),这里选择Executable

在这里插入图片描述

然后向导会自动新建CMakeLists.txt和main.cpp:

在这里插入图片描述

CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
project(demo VERSION 0.1.0)

include(CTest)
enable_testing()

add_executable(demo main.cpp)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

Main.cpp:

#include <iostream>

int main(int, char**) {
    std::cout << "Hello, world!\n";
}

自行创建CMake项目

其实就是自己编写上面的CMakeLists.txt和项目源码

可以从CMakeLists.txt中看到已经添加并启用了CTest

三、创建单元测试

1、添加需要测试的功能,比如建立一个func.h和func.cpp

func.h

#ifndef __FUNC_H_INCLUDE_
#define __FUNC_H_INCLUDE_

int Factorial(int n);
bool IsPrime(int n);

#endif

func.cpp

#include "func.h"

// Returns n! (the factorial of n).  For negative n, n! is defined to be 1.
int Factorial(int n) {
  int result = 1;
  for (int i = 1; i <= n; i++) {
    result *= i;
  }

  return result;
}

// Returns true if and only if n is a prime number.
bool IsPrime(int n) {
  // Trivial case 1: small numbers
  if (n <= 1) return false;

  // Trivial case 2: even numbers
  if (n % 2 == 0) return n == 2;

  // Now, we have that n is odd and n >= 3.

  // Try to divide n by every odd number i, starting from 3
  for (int i = 3;; i += 2) {
    // We only have to try i up to the square root of n
    if (i > n / i) break;

    // Now, we have i <= n/i < n.
    // If n is divisible by i, n is not prime.
    if (n % i == 0) return false;
  }

  // n has no integer factor in the range (1, n), and thus is prime.
  return true;
}

2、创建测试目录test,并在目录中添加CMakeLists.txt、tmain.cpp和单元测试代码test.cpp。

test/CMakeLists.txt

add_executable(t tmain.cpp test.cpp ../func.cpp)
target_link_libraries(t PRIVATE gtest)

add_test(NAME t COMMAND t)

tmain.cpp

#include <gtest/gtest.h>

int main()
{
    testing::InitGoogleTest();
    return RUN_ALL_TESTS();
}

test.cpp

#include <gtest/gtest.h>
#include "../func.h"


// Tests Factorial().

// Tests factorial of negative numbers.
TEST(FactorialTest, Negative) {
  // This test is named "Negative", and belongs to the "FactorialTest"
  // test case.
  EXPECT_EQ(1, Factorial(-5));
  EXPECT_EQ(1, Factorial(-1));
  EXPECT_GT(Factorial(-10), 0);

  // <TechnicalDetails>
  //
  // EXPECT_EQ(expected, actual) is the same as
  //
  //   EXPECT_TRUE((expected) == (actual))
  //
  // except that it will print both the expected value and the actual
  // value when the assertion fails.  This is very helpful for
  // debugging.  Therefore in this case EXPECT_EQ is preferred.
  //
  // On the other hand, EXPECT_TRUE accepts any Boolean expression,
  // and is thus more general.
  //
  // </TechnicalDetails>
}

// Tests factorial of 0.
TEST(FactorialTest, Zero) { EXPECT_EQ(1, Factorial(0)); }

// Tests factorial of positive numbers.
TEST(FactorialTest, Positive) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}

// Tests IsPrime()

// Tests negative input.
TEST(IsPrimeTest, Negative) {
  // This test belongs to the IsPrimeTest test case.

  EXPECT_FALSE(IsPrime(-1));
  EXPECT_FALSE(IsPrime(-2));
  EXPECT_FALSE(IsPrime(INT_MIN));
}

// Tests some trivial cases.
TEST(IsPrimeTest, Trivial) {
  EXPECT_FALSE(IsPrime(0));
  EXPECT_FALSE(IsPrime(1));
  EXPECT_TRUE(IsPrime(2));
  EXPECT_TRUE(IsPrime(3));
}

// Tests positive input.
TEST(IsPrimeTest, Positive) {
  EXPECT_FALSE(IsPrime(4));
  EXPECT_TRUE(IsPrime(5));
  EXPECT_FALSE(IsPrime(6));
  EXPECT_TRUE(IsPrime(23));
}

项目的目录结构如下:

在这里插入图片描述

3、修改根目录CMakeLists.txt

需要在CMakeLists.txt中引用test目录,添加如下命令:

add_subdirectory(test)

4、运行测试

如果MinGW中安装有gtest则可以在VSCode中执行Run CTest了:

在这里插入图片描述

输出如下:

在这里插入图片描述

当然,也可以在VSCode中选择测试目标t直接运行测试:

在这里插入图片描述

有没发现使用ctest并不能像直接运行测试目标t那样显示出详细的测试项目,那是因为在CMakeLists.txt中是使用的通用方法add_test(NAME t COMMAND t)添加的测试,其实CMake是直接支持gtest的,只需要把add_test(NAME t COMMAND t)换成下面两句即可:

include(GoogleTest)
gtest_add_tests(TARGET t)

可以看到各测试项目的情况了:

在这里插入图片描述

而且运行Run CTest后,VSCode中也会提示有几个测试用例和通过情况:

在这里插入图片描述

使用gtest_add_tests有一个问题:一旦测试用例改变,它就需要重新执行cmake,否则无法知道改变后的测试用例。 所以CMake添加了gtest_discover_tests指令,它通过调用编译后的执行程序并添加参数--gtest_list_tests来获取测试用例的,所以不需要重新执行CMake。

既然如此,那前面的tmain.cpp需要作修改以接收参数:

tmain.cpp

#include <gtest/gtest.h>

int main(int argc, char *argv[])
{
	testing::InitGoogleTest(&argc, argv);
	return RUN_ALL_TESTS();
}

此时把gtest_add_tests(TARGET t)替换成gtest_discover_tests(t)即可,修改后的CMakeLists.txt完整源码:

add_executable(t tmain.cpp test.cpp ../func.cpp)
target_link_libraries(t PRIVATE gtest)

include(GoogleTest)
gtest_discover_tests(t)

其实,gtest本身是带有一个main函数库的,只需要包链接gtest_main即可,不需要自己写tmain.cpp中的内容。可以把tmain.cpp删除,使用下面的CMakeLists.txt即可:

add_executable(t test.cpp ../func.cpp)
target_link_libraries(t PRIVATE gtest gtest_main)

include(GoogleTest)
gtest_discover_tests(t)

四、让CMake自动下载、编译依赖

前面有提到,要运行示例,必须要求安装了gtest,可以写入CMakeLists.txt中,使用CMake的find_package命令来查找,本例是需要GTest,添加find_package(GTest REQUIRED),并且是必须要安装有,所有后面添加了REQUIRED参数,注意必须是大写。

如果找不到GTest则会报错:
在这里插入图片描述
这种方式要求在MinGW中安装有GTest,可以使用MinGW命令:pacman -S mingw-w64-x86_64-gtest来安装。

当然更友好的方式是如果系统没有安装则自己下载源码进行编译引用,在根目录的CMakeLists.txt中添加:

cmake_policy(SET CMP0135 NEW)
find_package(GTest)
if(NOT GTest_FOUND)
message("GTest not found, download it...")
include(FetchContent)
FetchContent_Declare(googletest URL https://github.com/google/googletest/archive/refs/heads/main.zip)
FetchContent_MakeAvailable(googletest)
endif()

这里find_package没有添加REQUIRED参数来强制要求,只是检测,后面判断检测结果GTest_FOUND,如果没有找到则从指定URL下载(FetchContent_Declare)并编译(FetchContent_MakeAvailable),由于使用URL下载需要添加cmake_policy(SET CMP0135 NEW),不然会报警告:

[cmake]   The DOWNLOAD_EXTRACT_TIMESTAMP option was not given and policy CMP0135 is
[cmake]   not set.  The policy's OLD behavior will be used.  When using a URL
[cmake]   download, the timestamps of extracted files should preferably be that of
[cmake]   the time of extraction, otherwise code that depends on the extracted
[cmake]   contents might not be rebuilt if the URL changes.  The OLD behavior
[cmake]   preserves the timestamps from the archive instead, but this is usually not
[cmake]   what you want.  Update your project to the NEW behavior or specify the
[cmake]   DOWNLOAD_EXTRACT_TIMESTAMP option with a value of true to avoid this
[cmake]   robustness issue.

FetchContent_Declare也可以使用GIT_REPOSITORY从Git克隆下来,但是这种方式如果网络不好则比较慢。

注意为了使用这些高级指令,最好是安装最新的CMake版本,FetchContent最低要求3.11:

cmake_minimum_required(VERSION 3.11.0)

写得非常详细,如果觉得对你有帮助,欢迎点赞收藏!

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

CMake项目使用ctest+gtest进行单元测试 的相关文章

随机推荐

  • 微服务之API网关

    隐藏细节 现实生活中有很多隐藏细节的案例 比如我们平时用的电脑 当我们按电源按钮后电脑就自动开始启动了 对用户来讲很简单只需要知道按按钮就行 但电脑内部的工作原理其实是很复杂的一个流程 如果不隐藏细节会怎样 我想可能的结果就是电脑只能是特别
  • 【单片机毕业设计】【mcuclub-dz-057】基于单片机的跳绳设备的设计

    最近设计了一个项目基于单片机的跳绳设备的设计 与大家分享一下 一 基本介绍 项目编号 mcuclub dz 057 项目名 基于单片机的跳绳设备的设计 单片机 STC12C5A60S2 功能简介 1 通过MX1508利用PWM进行跳绳电机的
  • 关于python库文件安装失败的原因(附靠谱的快速安装库)

    在学习python的时候 适用pycharm突然发现库文件安装失败 尝试了几种方式 最终完美解决 我将我遇到的一些常见的问题进行了整理 并写了下来 以供日后自己学习 1 在pycharm中 添加库文件 File Settings Proje
  • PATH linux环境变量 LD_LIBRARY_PATH详解

    LD LIBRARY PATH详解 LD LIBRARY PATH是Linux环境变量名 该环境变量主要用于指定查找共享库 动态链接库 时除了默认路径之外的其他路径 非常多的软件没有root权限安装会比较困难 主要就是因为各种系统库文件 也
  • [转]Ubuntu系统GRUB无法启动全攻略

    1 装完XP Vista Win7后grub无法启动 有Live CD 这种问题是最经常遇到的 要解决问题 你需要一张ubuntu live cd 用live cd引导系统 直接按Ctrl Alt F1进入终端 输入sudo grub进入G
  • GP 常用数学函数

    1 1 绝对值函数 select abs 15 2 结果 15 2 1 2 开立方根 select cbrt 64 0 结果4 1 3 向上取整 select ceil 2 8 结果3 1 4 取商函数 select div 10 3 结果
  • 经典的笔试题解析:内存泄漏问题忘记free与非法访问的问题

    对于 高质量C C 编程 想必这个已经是早已成名的经典书籍了 在此 笔者借用两三个题目 在之前笔者就已经拙作两篇 有兴趣的各位老铁 可以进行欣赏一下啦 1 经典的笔试题解析 高质量C C 编程 链接为 经典的笔试题解析 高质量C C 编程
  • 编程辅助插件BitoAI使用指南(以VSCode开发环境为例安装并使用BitoAI插件从而提高生产效率)

    2023年是AI爆发元年 已经被各种AI工具 新闻轰炸了几个月 只有一种感觉 时间不够用 本文介绍编程辅助神器 Bito AI 本插件使用与ChatGPT相同的模型 目前免费 且拥有强大的辅助能力 可以数倍提升程序开发能力 并大大提高开发效
  • Python-re模块-正则表达式模块常用方法

    re模块介绍 Python的re模块提供了正则表达式的功能 可以用来进行高级的字符串匹配和处理 re模块的主要功能包括 编译正则表达式 使用re compile 可以编译正则表达式字符串 生成正则表达式对象 匹配字符串 使用正则表达式对象的
  • 2021年全国职业院校技能大赛 “大数据技术与应用”—模拟赛题(三)

    2021年全国职业院校技能大赛 大数据技术与应用 模拟赛题 三 文章适合了解大数据技术与应用技能大赛 赛题 文章在编写过程中难免有疏漏和错误 欢迎大佬指出文章的不足之处 更多内容请点进 Lino White 查看 未来的世界充满着各式各样的
  • 数据清洗规则

    数据清洗主要针对四种情况 1 缺失值 2 重复值 3 异常值 4 无用值 1 缺失值处理 重要性高 缺失率低 通过计算来填充 重要性高 缺失率高 可以从其他渠道数据进行补充 或者相关数据进行计算得出 重要性低 缺失率高 不处理或简单填充 重
  • 报错ReferenceError: Cannot access ‘xxxx‘ before initialization解决方案

    ReferenceError Cannot access xxxx before initialization 报这个错的原因其实就是和你声明的变量有关 我们都知道var存在变量提示 但是其实const和let在某种意义上也是存在变量提升的
  • Lua脚本学习

    1 介绍 Lua是一种轻量级的脚本语言 具有以下特点 简单易学 Lua语法简单 易于学习和使用 高效性 Lua的解释器非常快 可以在很短的时间内执行大量的代码 可嵌入性 Lua可以嵌入到其他应用程序中 作为脚本语言使用 可扩展性 Lua可以
  • 每日一题-错误的集合

    错误的集合 题目 示例 解题 题目 集合 s 包含从 1 到 n 的整数 不幸的是 因为数据错误 导致集合里面某一个数字复制了成了集合里面的另外一个数字的值 导致集合 丢失了一个数字 并且 有一个数字重复 给定一个数组 nums 代表了集合
  • Qt之电子时钟

    进一步认识Qt中的属性 我们现在再做一个小练习 去实现一个简易版电子时钟的效果 效果展示 新建项目 我们创建了lcdclock2类 其实是没有用到的 实际上是添加新一个C 文件来实现电子钟 添加C 文件 代码 clock h ifndef
  • 二进制、十进制、八进制、十六进制 各代表的英文字母是什么

    二进制是Binary 简写为B八进制是Octal 简写为O十进制为Decimal 简写为D十六进制为Hexadecimal 简写为H
  • 蓝桥杯第十一届省赛题解(Python)

    第一题 不出意料的签到题 遍历 法一 ans 0 for i in range 1 2021 for j in str i if j 2 ans 1 print ans 法二 s 0 for i in range 1 2021 s str
  • 经典的机器学习方面源代码库

    编程语言 搞实验个人认为当然matlab最灵活了 但是正版很贵 但是更为前途的是python numpy scipy matplotlib 和C C 这样组合既可搞研究 也可搞商业开发 易用性不比matlab差 功能组合更为强大 个人认为
  • IDEA比较两个jar包

    比较两个jar包是否一致 To compare two jar files select them in the Project view and press D 如需比较两个jar包 可以在Project视图里面选中他们 按 common
  • CMake项目使用ctest+gtest进行单元测试

    随着CMake工具越来越强大便捷 越来越多的C C 项目转而使用CMake来进行编译管理 它还提供了用于测试的ctest命令来执行项目中编写的单元测试 本文就以一个实例来介绍如何使用ctest来进行单元测试 一 环境准备 本文实例环境VSC