当前位置: 首页 > article >正文

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

随着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中也会提示有几个测试用例和通过情况:

在这里插入图片描述

四、让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)

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


http://www.kler.cn/a/10350.html

相关文章:

  • leetcode hot100【LeetCode 114.二叉树展开为链表】java实现
  • LeetCode【0031】下一个排列
  • Linux kernel 堆溢出利用方法(二)
  • stringUtils详细解释
  • 鸿蒙进阶篇-属性动画-animateTo转场动画
  • Vite初始化Vue3+Typescrpt项目
  • Vulnhub:Digitalworld.local (Development)靶机
  • C++ 23 实用工具(二)绑定工具
  • 10.字符串
  • 学习机器人SLAM导航核心技术(一)
  • 网络之广播域和冲突域
  • Qt5.12实战之规则DLL导出函数使用
  • 什么是分布式锁
  • 软考第二章 数据通信基础
  • 写博客8年与人生第一个502万
  • 空间复杂度
  • Makefile第十课:Makefile编译
  • 当你觉得生活快熬不下去时,请你读一读《活着》
  • Elastic Enterprise Search 8.7:新连接器、网络爬虫提取规则和搜索分析客户端测试版
  • Golang数据类型比较
  • MongoDB
  • 使用 ArcGIS Pro 进行土地利用分类的机器学习和深度学习
  • SpringBoot常见的的面试点
  • ArrayList、LinkedList与Vector的区别?
  • 【自用】HTML笔记
  • VS Code 快捷键