【020】使用Google Test进行 C++ 单元测试:基于 CMake 和 FetchContent 的现代方法
文章目录
- 一、前言
- 二、准备工作
- 三、配置CMakeLists.txt
- 四、构建与测试
- 五、工作原理深度解析
- 六、更多信息与替代方案
一、前言
在现代C++项目开发中,单元测试是确保代码质量和可维护性的关键环节。Google Test(GTest)作为一款功能强大、广受欢迎的C++测试框架,提供了丰富的断言、测试夹具和测试发现机制。

本文演示如何利用CMake的FetchContent模块,优雅地集成和使用Google Test进行单元测试。不仅会展示具体步骤,还会深入探讨FetchContent的工作原理,并提及它与ExternalProject_Add的区别,帮助更好地理解其在项目管理中的优势。
二、准备工作
在开始集成Google Test之前,需要准备一些待测试的 C++ 代码以及相应的测试用例。
以一个简单的整数求和函数为例。
sum_integers.hpp: 头文件,声明求和函数。sum_integers.cpp: 源文件,实现求和函数。main.cpp: 主应用程序文件,用于演示如何使用sum_integers库。
sum_integers.hpp
#ifndef SUM_INTEGERS_HPP
#define SUM_INTEGERS_HPP#include <numeric>
#include <vector>
/*** @brief Calculates the sum of integers in a given range.* @param integers An initializer list of integers.* @return The sum of the integers.*/
long long sum_integers(std::initializer_list<int> integers);#endif // SUM_INTEGERS_HPP
sum_integers.cpp
#include "sum_integers.hpp"
#include <numeric> // For std::accumulatelong long sum_integers(std::initializer_list<int> integers)
{long long sum = 0;for (int i : integers) {sum += i;}return sum;// Alternatively, using std::accumulate:// return std::accumulate(integers.begin(), integers.end(), 0LL);
}
main.cpp
#include "sum_integers.hpp"
#include <iostream>
#include <vector>int main()
{std::initializer_list<int> numbers1 = {1, 2, 3, 4, 5};long long result1 = sum_integers(numbers1);std::cout << "Sum of {1, 2, 3, 4, 5} = " << result1 << std::endl; // Expected: 15std::initializer_list<int> numbers2 = {10, -5, 2, -7};long long result2 = sum_integers(numbers2);std::cout << "Sum of {10, -5, 2, -7} = " << result2 << std::endl; // Expected: 0return 0;
}
Google Test测试用例 (test.cpp): test.cpp 文件包含单元测试代码。它会包含 sum_integers.hpp 来访问待测试函数,并包含 gtest/gtest.h 来使用Google Test框架。
#include "sum_integers.hpp" // 包含待测试函数的头文件
#include "gtest/gtest.h" // 包含Google Test框架的头文件
#include <vector>// Google Test的入口点。
// 这是所有Google Test可执行文件都需要的标准main函数。
int main(int argc, char **argv) {// 初始化Google Test框架。它会解析命令行参数,并设置测试环境。::testing::InitGoogleTest(&argc, argv);// 运行所有定义的测试。return RUN_ALL_TESTS();
}// 定义一个测试套件(Test Suite)名为 'example',其中包含一个测试用例 'sum_zero'。
TEST(example, sum_zero) {// 定义一个整数列表,其和为0。auto integers = {1, -1, 2, -2, 3, -3};// 调用待测试函数。auto result = sum_integers(integers);// 使用ASSERT_EQ断言验证结果是否等于预期值0。// 如果不相等,测试将立即失败,并终止当前测试函数。ASSERT_EQ(result, 0);
}// 定义另一个测试用例 'sum_five',属于 'example' 测试套件。
TEST(example, sum_five) {// 定义一个整数列表,其和为15。auto integers = {1, 2, 3, 4, 5};// 调用待测试函数。auto result = sum_integers(integers);// 使用ASSERT_EQ断言验证结果是否等于预期值15。ASSERT_EQ(result, 15);
}
与将Google Test的源代码直接放入项目仓库不同,利用CMake的FetchContent模块在配置时自动下载并构建Google Test,从而保持项目自身的轻量级。
三、配置CMakeLists.txt
以下步骤详细描述了如何设置 CMakeLists.txt 文件,以使用Google Test编译测试可执行文件。
CMakeLists.txt
# 设置CMake的最低版本要求。FetchContent模块从3.11版本开始可用。
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)# 定义项目名称和使用的语言。
project(recipe-22 LANGUAGES CXX)# 设置C++标准为C++11。
set(CMAKE_CXX_STANDARD 11)
# 禁用C++编译器扩展(推荐,以确保代码的可移植性)。
set(CMAKE_CXX_EXTENSIONS OFF)
# 强制要求使用指定的C++标准。
set(CMAKE_CXX_STANDARD_REQUIRED ON)# 针对Windows平台,如果需要导出符号,可以设置此选项。
# 这通常用于构建DLL时,确保所有公共符号都被导出。
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)# 1. 定义我们的库:sum_integers
# add_library命令用于创建静态或动态库。
add_library(sum_integers sum_integers.cpp)# 2. 定义主应用程序可执行文件
# add_executable命令用于创建可执行文件。
add_executable(sum_up main.cpp)
# target_link_libraries命令用于将库链接到可执行文件或另一个库。
# sum_up 可执行文件需要链接 sum_integers 库。
target_link_libraries(sum_up sum_integers)# 3. 启用/禁用单元测试的选项
# option命令用于定义一个用户可配置的选项,默认值为ON。
option(ENABLE_UNIT_TESTS "Enable unit tests" ON)
# message命令用于在CMake配置过程中输出信息。
message(STATUS "Unit tests enabled: ${ENABLE_UNIT_TESTS}")# 只有当ENABLE_UNIT_TESTS为ON时,才执行后续与测试相关的CMake代码。
if(ENABLE_UNIT_TESTS)# 4. 引入FetchContent模块# FetchContent模块允许在配置时下载外部项目。include(FetchContent)# 5. 声明要获取的Google Test项目# FetchContent_Declare用于声明一个外部内容源。FetchContent_Declare(googletest # 外部项目的名称,后续会用到这个名称来引用它。GIT_REPOSITORY https://github.com/google/googletest.git # Git仓库URL。GIT_TAG release-1.8.0 # 指定要下载的Git标签或提交哈希,确保版本一致性。# GIT_SHALLOW TRUE # 可选:进行浅克隆,减少下载时间。)# 6. 查询Google Test项目是否已被填充# FetchContent_GetProperties用于获取已声明内容的属性,# 例如是否已被下载和配置(POPULATED)。FetchContent_GetProperties(googletest)# 7. 如果Google Test尚未被填充,则进行下载和配置if(NOT googletest_POPULATED)# FetchContent_Populate用于执行下载和解压操作。# 它会设置 ${googletest_SOURCE_DIR} 和 ${googletest_BINARY_DIR} 变量。FetchContent_Populate(googletest)# 针对Visual Studio的特定配置:# 强制Google Test使用共享运行时库(CRT),以避免与主项目不一致。set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)# 禁用Google Test使用PThreads,避免潜在的冲突或不必要的依赖。set(gtest_disable_pthreads ON CACHE BOOL "" FORCE)# 将下载的Google Test项目添加为当前项目的一个子目录。# 这会处理Google Test自身的CMakeLists.txt,并使其内部定义的target可用。# 例如,它会定义 gtest, gtest_main, gmock, gmock_main 等目标。add_subdirectory(${googletest_SOURCE_DIR} # Google Test的源代码目录。${googletest_BINARY_DIR} # Google Test的构建目录。)# 针对MSVC编译器的特定警告处理:# 解决在MSVC下编译Google Test时可能出现的std::tr1命名空间弃用警告。if(MSVC)foreach(_tgt gtest gtest_main gmock gmock_main)target_compile_definitions(${_tgt}PRIVATE"_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING")endforeach()endif()endif()# 8. 定义单元测试可执行文件# 创建一个名为 cpp_test 的可执行文件。add_executable(cpp_test "") # 初始为空,后续通过target_sources添加源文件。# 9. 指定测试可执行文件的源文件# target_sources命令用于向目标添加源文件。target_sources(cpp_testPRIVATE # PRIVATE表示这些源文件仅用于构建此目标,不影响其他目标。test.cpp # 我们的测试用例文件。)# 10. 链接测试可执行文件所需的库# cpp_test 需要链接 sum_integers 库(因为它测试这个库)# 还需要链接 gtest_main 库,这是Google Test提供的包含main函数的库。target_link_libraries(cpp_testPRIVATEsum_integers # 链接我们自己定义的库。gtest_main # 链接Google Test的主库,它包含了测试运行器。)# 11. 启用并添加测试到CTest# enable_testing() 必须在 add_test() 之前调用,以启用CTest测试发现。enable_testing()# add_test命令用于向CTest注册一个测试。add_test(NAME google_test # 测试的名称,在ctest输出中显示。COMMAND $<TARGET_FILE:cpp_test> # 运行测试的命令,这里使用CMake生成的可执行文件路径。)
endif()
要注意,CMake 3.11才能使用FetchContent模块。
引入了一个检查ENABLE_UNIT_TESTS的判断。目的是在没有网络连接时,也能使用Google Test。
四、构建与测试
完成 CMakeLists.txt 的配置后,可以开始构建和运行测试了。
-
创建构建目录并进入: 建议在项目根目录外创建一个独立的
build目录,以保持源代码目录的整洁。mkdir -p build cd build -
配置项目: 运行
cmake ..命令来配置项目。这将触发FetchContent下载 Google Test。cmake ..在配置过程中,会看到类似以下输出,表明Google Test正在被下载和配置:
-- The CXX compiler identification is GNU 11.4.0 -- The C compiler identification is GNU 11.4.0 -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ - skipped -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/bin/cc - skipped -- Unit tests enabled: ON -- Fetching googletest -- Performing download step (git clone) for 'googletest' -- googletest-src/CMakeLists.txt -- ... (Google Test自身的配置输出) ... -- Configuring done -- Generating done -
构建项目: 运行
cmake --build .命令来编译所有目标,包括主应用程序和测试可执行文件。cmake --build . -
运行单元测试 (使用 CTest): CTest 是 CMake 的测试运行器,它会发现并执行
add_test定义的所有测试。ctest输出:
Test project /home/fly/workspace/cmake-learning-code/recipe_22_gtest/build Start 1: google_test 1/1 Test #1: google_test ...................... Passed 0.00 sec100% tests passed, 0 tests failed out of 1Total Test time (real) = 0.01 sec -
直接运行测试可执行文件: 也可以直接运行由Google Test生成的测试可执行文件
cpp_test,它会提供Google Test自身更详细的输出。./cpp_test输出:
[==========] Running 2 tests from 1 test case. [----------] Global test environment set-up. [----------] 2 tests from example [ RUN ] example.sum_zero [ OK ] example.sum_zero (0 ms) [ RUN ] example.sum_five [ OK ] example.sum_five (0 ms) [----------] 2 tests from example (0 ms total)[----------] Global test environment tear-down [==========] 2 tests from 1 test case ran. (0 ms total) [ PASSED ] 2 tests.
五、工作原理深度解析
FetchContent 模块是CMake 3.11版本引入的一个强大功能,它允许在CMake的配置阶段下载外部项目。这与传统的 ExternalProject_Add() 命令有所不同,ExternalProject_Add() 通常在构建阶段执行下载操作。FetchContent 的优势在于,一旦外部项目被下载,它就可以立即被集成到主项目的构建系统中,就像一个本地子目录一样,从而实现更流畅的工作流。
FetchContent 的核心机制:
(1)声明外部内容 (FetchContent_Declare):
FetchContent_Declare(googletestGIT_REPOSITORY https://github.com/google/googletest.gitGIT_TAG release-1.8.0
)
FetchContent_Declare 用于告诉CMake想要获取一个名为 googletest 的外部项目。可以指定多种来源,例如Git仓库(GIT_REPOSITORY 和 GIT_TAG/GIT_BRANCH/GIT_COMMIT)、Subversion、Mercurial,甚至直接的HTTP(S)文件下载(URL)。这使得项目可以灵活地从各种远程源获取依赖。有关可用选项的详细内容可参考 ExternalProject_Add 命令的文档,因为 FetchContent 在底层使用了 ExternalProject 的下载机制。
(2)检查与填充 (FetchContent_GetProperties 和 FetchContent_Populate):
FetchContent_GetProperties(googletest)
if(NOT googletest_POPULATED)FetchContent_Populate(googletest)# ...
endif()
FetchContent_GetProperties(googletest) 会查询 googletest 这个外部项目是否已经被下载和“填充”(populated)。它会设置一个变量,例如 googletest_POPULATED。
if(NOT googletest_POPULATED) 这个条件判断至关重要,它确保 FetchContent_Populate() 只在外部项目尚未下载时才执行。如果多次调用 FetchContent_Populate() 而不进行此检查,CMake 将会报错。
当 FetchContent_Populate(googletest) 被调用时,CMake 会执行实际的下载操作,并将内容解压到构建目录下的一个临时位置。同时,它会定义两个重要的变量:
${googletest_SOURCE_DIR}: 指向下载的Google Test源代码的路径。${googletest_BINARY_DIR}: 指向Google Test的构建目录(CMake会在这里生成构建文件)。
(3)集成子项目 (add_subdirectory):
add_subdirectory(${googletest_SOURCE_DIR}${googletest_BINARY_DIR}
)
这是将下载的外部项目集成到主项目中的关键步骤。由于Google Test本身也是一个CMake项目,它包含自己的 CMakeLists.txt 文件。add_subdirectory() 命令会处理Google Test的 CMakeLists.txt,使其内部定义的目标(如 gtest、gtest_main、gmock、gmock_main 等)在主项目中变得可用。这意味着您现在可以直接在主项目的 CMakeLists.txt 中引用这些目标。
(4)链接测试目标 (target_link_libraries):
target_link_libraries(cpp_testPRIVATEsum_integersgtest_main
)
示例创建了一个名为 cpp_test 的测试可执行文件。这个可执行文件需要链接到自己的 sum_integers 库(因为它要测试这个库的功能),同时还需要链接到Google Test提供的 gtest_main 库。gtest_main 库包含了Google Test框架的 main 函数,负责初始化测试环境并运行所有测试。通过这种方式,避免了手动编写 main 函数来启动测试。
FetchContent 与 ExternalProject_Add 的对比:
FetchContent: 主要用于在配置时获取内容。一旦内容被获取,它就可以通过add_subdirectory立即集成到当前构建中。这非常适合那些本身也是CMake项目的依赖,或者需要在配置时就可用的依赖。它使得构建过程更像是一个单通道(single-pass)过程。ExternalProject_Add: 主要用于在构建时获取和构建外部项目。它会创建一个独立的CMake目标,该目标在主项目构建时负责下载、配置和构建外部项目。这更适合那些非常庞大、需要独立构建步骤的依赖,或者那些不需要在配置时立即访问其内部目标的依赖。
本质上,FetchContent 封装并简化了 ExternalProject_Add 的某些功能,使其更易于在配置时集成外部CMake项目。
使用 GIT_TAG 或 GIT_COMMIT 来指定Google Test的版本是一个良好的实践。这确保了团队成员和CI/CD系统在构建时总是使用相同且经过验证的Google Test版本,从而提高了构建的确定性和可重复性。如果需要升级Google Test,只需修改 FetchContent_Declare 中 GIT_TAG 的值,而无需更改其他任何代码。
六、更多信息与替代方案
FetchContent 模块提供了丰富的选项和功能,以满足不同的外部内容获取需求。可以查阅CMake官方文档以获取更深入的了解:https://cmake.org/cmake/help/v3.11/module/FetchContent.html
除了 FetchContent,还有其他几种常见的Google Test集成方式:
-
系统安装并使用
FindGTest模块:如果Google Test已经安装在系统上(例如通过包管理器),可以使用CMake的find_package(GTest)命令来查找它。这需要Google Test被安装到标准路径或通过环境变量指定。一旦找到,FindGTest模块会设置相应的变量和导入目标供链接。
相关文档:https://cmake.org/cmake/help/v3.5/module/FindGTest.html -
CMake的
GoogleTest模块和gtest_add_tests函数:从CMake 3.9版本开始,CMake 提供了一个专门的GoogleTest模块。这个模块提供了一个方便的gtest_add_tests函数,它可以自动扫描源代码中定义的Google Test宏(如TEST,TEST_F等),并自动为它们创建add_test条目,从而简化了测试的注册过程。
相关文档:https://cmake.org/cmake/help/v3.9/module/GoogleTest.html -
直接将Google Test源代码添加到项目:最直接但不推荐的方式是将Google Test的源代码(或其子模块)直接添加到项目仓库中,然后使用
add_subdirectory()命令将其作为本地子项目处理。这种方式会增加您项目仓库的大小,且不利于版本管理和共享依赖。
Google Test本身是一个功能非常丰富的测试框架,提供了除了基本断言之外的许多高级特性,例如:
- 测试夹具 (Test Fixtures): 用于为多个测试用例设置和清理共同的测试环境。
- 参数化测试 (Parameterized Tests): 允许使用不同的输入数据多次运行同一个测试逻辑。
- 死亡测试 (Death Tests): 用于测试程序是否在预期的情况下终止(例如,调用
abort())。 - 事件监听器 (Event Listeners): 允许您自定义测试运行时的行为,例如生成自定义报告。
- 模拟对象 (Mock Objects) 与 Google Mock: Google Mock 是 Google Test 的一个配套框架,用于创建和使用模拟对象,这在测试依赖于复杂外部组件的代码时非常有用。
可以访问Google Test的官方GitHub仓库获取最新信息和更详细的文档:https://github.com/google/googletest

