☰
CMake 集成 GoogleTest 实战:FindGTest 模块的导入目标、配置提示与 CTest 深度整合
2026/10/9 1:26:35 网站建设 项目流程
  • 构建工具
  • 开发工具
  • CLI

【免费下载链接】CMake

Mirror of CMake upstream repository

项目地址:https://gitcode.com/gh_mirrors/cm/CMake
点击查看免费下载

本文以 CMake 官方 FindGTest 模块(源码位于 Modules/FindGTest.cmake)为核心,系统讲解如何通过find_package(GTest)在 CMake 项目中定位并链接 Google C++ 测试与模拟框架(GoogleTest / GoogleMock)。读完本文,你将掌握:四个官方导入目标(GTest::gtest、GTest::gtest_main、GTest::gmock、GTest::gmock_main)的正确用法、GTEST_ROOT与GTEST_MSVC_SEARCH两个配置提示的语义、debug/release 变体自动选择的底层机制,以及如何用gtest_discover_tests与gtest_add_tests把 GoogleTest 用例无缝接入 CTest。

模块概览:一行命令找到 GoogleTest

FindGTest 模块是 CMake 提供的标准查找模块,用于定位 GoogleTest——Google 的 C++ 测试与模拟框架。其核心调用形式非常简单:

find_package(GTest [...])

GoogleTest 框架同时包含 GoogleMock——一个用于编写和使用 C++ 模拟(mock)类的库。需要注意的是,在某些系统上,GoogleMock 可能作为独立包分发,此时只有 gtest 库能被找到,而 gmock 相关目标不会出现。

该模块的一大特色是运行时库变体自动选择:当 GoogleTest 与 GoogleMock 同时存在 debug 和 release(optimized)两种变体时,模块会根据当前构建配置(见 Build Configurations)自动挑选合适的变体进行链接,无需开发者手工判断。

双模式查找:Config 模式优先,Module 模式兜底

自 CMake 3.20 起,FindGTest 采用"先 Config 模式、后 Module 模式"的双层查找策略:

  1. Config 模式(优先):如果 GoogleTest 是用其自身的 CMake 构建系统构建并安装的,它会提供一个包配置文件GTestConfig.cmake。模块默认先以find_package(GTest QUIET NO_MODULE)(见 Modules/FindGTest.cmake)寻找该文件;一旦命中,直接采用其返回结果,不再做任何额外搜索。
  2. Module 模式(兜底):若上游配置文件不存在,模块回退到经典 Module 模式,在标准位置搜索头文件与库文件。

这意味着:优先使用官方 CMake 构建产物,只有在无法找到 Config 文件时才依赖手工安装或预编译包的传统布局。从源码看,Config 模式命中后模块会直接return()(Modules/FindGTest.cmake),并将GTEST_LIBRARIES、GTEST_MAIN_LIBRARIES指向GTest::gtest与GTest::gtest_main,同时补建向后兼容目标。

官方导入目标(Imported Targets)

模块提供四个命名空间导入目标,封装了使用 GoogleTest/GoogleMock 所需的完整使用要求(include 目录、链接库、编译定义):

目标引入版本说明
GTest::gtest3.20封装 GoogleTest 核心gtest库的使用要求,提供测试框架核心功能
GTest::gtest_main3.20封装gtest_main库,自带main()函数,无需手写入口
GTest::gmock3.23封装 GoogleMockgmock库,提供编写模拟类的设施
GTest::gmock_main3.23封装gmock_main库,自带main(),可直接运行 GoogleMock 测试

使用GTest::gtest_main或GTest::gmock_main有一个重要前提:只有当你希望 GoogleTest/GoogleMock 来提供可执行文件的main()函数时才链接它们。如果你的项目自己实现了main()(例如需要自定义初始化逻辑),则只链接GTest::gtest或GTest::gmock,否则会出现main重复定义。

从源码实现看,这些导入目标的构建遵循统一的流程(Modules/FindGTest.cmake):

  • GTest::gtest会额外链接Threads::Threads(通过find_package(Threads QUIET)获得),保证多线程测试正确链接线程库;
  • 当检测到 gtest 是共享库(SHARED)时,会自动注入编译定义GTEST_LINKED_AS_SHARED_LIBRARY=1;gmock 同理注入GMOCK_LINKED_AS_SHARED_LIBRARY=1;
  • GTest::gtest_main通过INTERFACE_LINK_LIBRARIES依赖GTest::gtest,GTest::gmock_main依赖GTest::gmock,因此单独链接 main 目标即可传递获得底层库。

结果变量与配置提示

结果变量:GTest_FOUND

模块定义的结果变量为GTest_FOUND(CMake 3.3 起),布尔值,表示是否成功找到 GoogleTest:

find_package(GTest) if(GTest_FOUND) message(STATUS "GoogleTest 已找到") endif()

更常见的是使用REQUIRED关键字,未找到时配置阶段直接报错终止:

find_package(GTest REQUIRED)

配置提示一:GTEST_ROOT

GTEST_ROOT用于指定 GoogleTest 安装根目录,仅在 Module 模式下生效(Config 模式由GTestConfig.cmake自行定位)。它可以作为 CMake 变量传入,也可以设置为环境变量。从源码可见,头文件与库文件的搜索都把它作为 HINTS(Modules/FindGTest.cmake、Modules/FindGTest.cmake):

set(GTEST_ROOT "/opt/gtest" CACHE PATH "GoogleTest 安装根目录") find_package(GTest REQUIRED)

或通过环境变量:

export GTEST_ROOT=/opt/gtest cmake -S . -B build

模块会搜索${GTEST_ROOT}/include(头文件gtest/gtest.h)以及${GTEST_ROOT}/lib及平台相关子目录(库文件)。

配置提示二:GTEST_MSVC_SEARCH

仅在使用 MSVC 编译器时相关,GTEST_MSVC_SEARCH控制基于运行时库链接模型查找哪种 GoogleTest 构建变体,同样只在 Module 模式生效:

  • MD(默认):查找链接动态 C 运行时的共享库变体,通常以/MD或/MDd(分别对应 Release/Debug)编译。MSVC 下模块会优先搜索gtest-md、gtest_main-md、gmock-md等带-md后缀的库名,并附带msvc/gtest-md/Debug、msvc/gtest-md/Release、msvc/x64/Debug、msvc/2010/gtest-md/*等路径后缀(Modules/FindGTest.cmake);
  • MT:查找链接静态 C 运行时的静态库变体,通常以/MT或/MTd编译。对应搜索gtest、gtest_main等不带-md后缀的库名,路径后缀为msvc/gtest/*、msvc/2010/gtest/*(Modules/FindGTest.cmake)。

注意选择与你的目标程序一致的模式,避免 C 运行时(CRT)不一致导致的链接错误。若未显式定义,模块默认取MD(Modules/FindGTest.cmake)。

弃用项:为什么建议只使用导入目标

模块保留了一批向后兼容的变量与目标,供旧项目平滑迁移,但新项目不应使用:

弃用项弃用版本替代方案
GTEST_INCLUDE_DIRS4.1使用GTest::gtest,其通过INTERFACE_INCLUDE_DIRECTORIES暴露头文件路径
GTEST_LIBRARIES4.1使用GTest::gtest
GTEST_MAIN_LIBRARIES4.1使用GTest::gtest_main
GTEST_BOTH_LIBRARIES4.1同时使用GTest::gtest与GTest::gtest_main
GTEST_FOUND4.2使用GTest_FOUND(二者值相同)
GTest::GTest(目标)3.20使用GTest::gtest
GTest::Main(目标)3.20使用GTest::gtest_main

其中GTEST_INCLUDE_DIRS只在 Module 模式下保证可用;GTEST_LIBRARIES还要求项目自行负责链接合适的线程库。源码中__gtest_define_backwards_compatible_library_targets(Modules/FindGTest.cmake)会把GTest::GTest与GTest::Main实现为 INTERFACE IMPORTED 目标,转发到新的GTest::gtest与GTest::gtest_main,保证老代码依然可用。

从源码看,模块内部用__gtest_append_debugs(Modules/FindGTest.cmake)将找到的XXX_DEBUG变体与普通变体组合为optimized ... debug ...形式,这正是"按构建配置自动选择 debug/release 变体"的底层实现;在 Windows 上还通过__gtest_determine_windows_library_type检查库文件内是否包含.dll字符串来判定 SHARED/UNKNOWN 类型(Modules/FindGTest.cmake),进而决定使用IMPORTED_IMPLIB还是IMPORTED_LOCATION导入(Modules/FindGTest.cmake)。

实战用法一:基础查找与链接

最简用法——查找并链接核心测试框架:

find_package(GTest REQUIRED) target_link_libraries(foo PRIVATE GTest::gtest)

进阶用法——链接gtest_main以自动获得main()并注册测试:

enable_testing() find_package(GTest REQUIRED) add_executable(foo foo.cc) target_link_libraries(foo PRIVATE GTest::gtest GTest::gtest_main) add_test(NAME AllTestsInFoo COMMAND foo)

这里两条链接缺一不可:GTest::gtest_main提供main()入口,而GTest::gtest仍需要显式链接,因为测试代码直接使用了 gtest 提供的断言宏与测试框架 API。良好实践是:直接使用的库就显式链接,不要依赖传递依赖。

该用法与 CMake 官方测试用例一致,见 Tests/FindGTest/Test/CMakeLists.txt——其中既验证了旧式目标GTest::Main(对应测试test_gtest_tgt)、新式目标GTest::gtest_main(test_gtest_tgt_upstream)、旧式变量GTEST_INCLUDE_DIRS+GTEST_BOTH_LIBRARIES(test_gtest_var),也验证了GTest::gmock_main(test_gmock_tgt);测试源文件 Tests/FindGTest/Test/main.cxx 使用TEST(FindCMake, LinksAndRuns)断言测试确实被链接并可运行。

实战用法二:与 CTest 深度整合

FindGTest 模块常与 GoogleTest 模块(源码位于 Modules/GoogleTest.cmake,自 CMake 3.9 提供,include(GoogleTest)加载)配合,借助gtest_add_tests与gtest_discover_tests两条命令把 GoogleTest 用例自动注册为 CTest 测试。注意:CMake 3.9 之前gtest_add_tests定义在 FindGTest 模块内部,此后已独立为 GoogleTest 模块。

find_package(GTest) target_link_libraries(example PRIVATE GTest::gtest GTest::gtest_main) include(GoogleTest) gtest_discover_tests(example)

两条命令机制不同,各有取舍:

  • gtest_add_tests:在 CMake 配置期扫描源码文件,用正则识别TEST(...)等宏。优点是测试在 CMake 期即已声明,方便设置测试属性(如TIMEOUT);在交叉编译环境中始终可用;缺点是对参数化测试的"拆分"不彻底,且源码变化后需要重新运行 CMake 才能刷新测试列表。典型用法:
include(GoogleTest) add_executable(FooTest FooUnitTest.cxx) gtest_add_tests(TARGET FooTest TEST_SUFFIX .noArgs TEST_LIST noArgsTests ) gtest_add_tests(TARGET FooTest EXTRA_ARGS --someArg someValue TEST_SUFFIX .withArgs TEST_LIST withArgsTests ) set_tests_properties(${noArgsTests} PROPERTIES TIMEOUT 10) set_tests_properties(${withArgsTests} PROPERTIES TIMEOUT 20)
  • gtest_discover_tests(CMake 3.10 起):在构建期或测试期运行测试可执行文件并解析--gtest_list_tests输出,能获得完整测试列表(包括参数化测试的各个实例化),测试增删改无需重跑 CMake。缺点是需要可执行文件能运行(交叉编译时需正确设置CROSSCOMPILING_EMULATOR),且测试不在 CMake 期可用、设置单个测试属性不便(可借助PROPERTIES整体设置,或通过TEST_INCLUDE_FILES目录属性配合外部 CTest 脚本)。其常用选项包括:TEST_PREFIX/TEST_SUFFIX修改 CTest 测试名、TEST_FILTER(3.22+)传入--gtest_filter表达式、NO_PRETTY_TYPES/NO_PRETTY_VALUES关闭类型/值参数化测试的美化命名、DISCOVERY_TIMEOUT(默认 5 秒)、XML_OUTPUT_DIR(3.18+,配合--gtest_output=xml:避免并行执行时 XML 文件竞争写入)、DISCOVERY_MODE(3.18+,POST_BUILD构建期发现 /PRE_TEST测试前在目标环境发现,默认值可由CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE全局控制)以及DISCOVERY_EXTRA_ARGS(3.31+)。

两者默认生成的 CTest 测试名与 GoogleTest 名一致(形如suite.testcase)。就效率而言,为每个用例单独建 CTest 测试意味着公共的 setup/teardown 逻辑无法在多个用例间共享,但换来的是 CTest 能获得更细粒度的通过/失败信息,通常收益更大。

小结

FindGTest 模块提供了从"发现"到"链接"再到"测试注册"的一站式 GoogleTest 集成方案:Config 模式优先确保优先使用官方 CMake 构建产物;四个命名空间导入目标封装了 include、链接、线程与共享库编译定义等全部使用要求;GTEST_ROOT与GTEST_MSVC_SEARCH两个提示变量覆盖了手工安装与 MSVC 运行时模型两大典型场景;配合 GoogleTest 模块的gtest_add_tests/gtest_discover_tests,即可把 GoogleTest 用例无缝纳入 CTest 测试编排。新项目请一律使用导入目标,远离已弃用的变量与目标。

  • 构建工具
  • 开发工具
  • CLI

【免费下载链接】CMake

Mirror of CMake upstream repository

项目地址:https://gitcode.com/gh_mirrors/cm/CMake
点击查看免费下载

相关推荐

上一篇:Stretchly终极指南:免费开源的健康休息提醒应用,告别屏幕疲劳
下一篇:终极指南:3分钟免费掌握VideoDownloadHelper网页视频下载技巧

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询