1. 为什么选择在CMake项目中引入GoogleTest
做C++开发的朋友应该都有这个体会:项目一旦过了"玩具阶段",没有一套正经的测试框架兜底,改代码心里就没底。我见过太多项目,测试代码就是一堆main函数里printf打印,每次改动全靠肉眼对比输出,等模块多了之后根本维护不过来。GoogleTest作为C++社区使用最广的测试框架之一,配合CMake这套构建系统,基本已经成了现代C++项目的标配组合。
我自己在几个跨平台项目里(Windows、Linux、macOS都跑过)把GoogleTest接进CMake工程,踩过不少坑,也总结了一套比较顺手的接入方式。这篇就把我实际用下来的方案、配置细节和避坑经验一起分享出来,希望对正在折腾这事儿的朋友有帮助。
这套方案能解决的问题很明确:一是把测试代码和业务代码的构建解耦,二是提供统一的断言、测试固件、参数化测试能力,三是不用手动管理测试二进制和CTest的关系。适合的读者是已经会用CMake组织项目、但还没系统接入测试框架的C++开发者,也适合想在老项目里补测试但不知道怎么下手的人。
2. 整体设计思路与方案选型
2.1 GoogleTest在CMake生态里的几种接入方式
先说结论:接入GoogleTest的方式,网上能搜到的大概有三条路线。
第一种是用FetchContent在配置阶段从GitHub拉取源码编译。这种方式的好处是真的一行命令搞定,不依赖系统预装库,CMake会自动处理下载、编译、链接全过程。缺点是第一次配置需要联网,GitHub访问不稳定的时候真想砸电脑。
第二种是find_package(GTest)配合系统安装的库。优势是干净,但系统包的版本往往偏旧,而且不同发行版安装路径有差异,跨平台时容易踩到路径坑。
第三种是直接把GoogleTest源码作为子目录(subdirectory),通过add_subdirectory(googletest)编进来。这个方式其实是最老派的做法,好处是版本可控、离线可用,但也要求你把第三方源码塞进自己的仓库,仓库体积会变大。
我个人现在的选择是:新项目优先用FetchContent,老项目或者长期稳定且不想依赖外网的项目,用add_subdirectory。这篇主要讲FetchContent方案,因为它最贴合"CMake项目引入GoogleTest"这个核心诉求,也是对新手最友好的方式。
2.2 为什么默认绑定CTest
这里有一点容易被忽略:GoogleTest本身只负责写测试用例和跑测试,但"哪些测试要跑、什么时候跑"其实是CTest在管。CMake里有个include(CTest)指令,配合enable_testing(),就能把GoogleTest的测试用例自动注册到ctest命令下。
这么设计的好处是,你不需要手动写一堆shell脚本去遍历执行每个测试二进制,跑一次ctest就能把所有注册的测试都执行完,还能自动带出输出重定向、超时控制、并行执行这些能力。如果你用CI,CTest直接一行命令就能对接主流的持续集成流程,省去很多工装工作。
我在接入时有个习惯:始终让add_test和gtest_discover_tests并存,前者的调用用于定义测试名称,后者负责把GoogleTest里的每个TEST用例自动展开注册到CTest。这样在ctest里能按用例粒度筛选执行,排查问题方便得多。
2.3 版本选择与C++标准的一致性考量
引入GoogleTest时最容易踩的一个坑是C++标准不匹配。老版本GoogleTest对C++11的依赖比较重,新版本(比如1.12+)已经把默认标准要求调到了C++14,如果项目本身还在C++11甚至C++98上跑,直接FetchContent最新版本可能会在编译阶段就报一堆static_assert错误。
我遇到过最典型的情况:一个老项目用的C++11,把GoogleTest的版本从1.10升到1.14之后,编译测试代码时报了一堆模板相关的错误。最后解决方式是给GoogleTest单独设置编译标准,不让它继承项目的标准设定。具体做法在后面的配置章节会详细展开。
这里给一个比较稳的版本表参考:
| GoogleTest版本 | 最低C++标准要求 | 建议场景 |
|---|---|---|
| 1.10.x | C++11 | 老项目、C++11为主的项目 |
| 1.12.x | C++14 | 大多数现代项目 |
| 1.14.x / 1.15.x | C++14 | 新项目,需要最新断言语法的 |
版本和标准这里千万不能图省事,直接在FetchContent里写GIT_TAG main拉最新提交,那等于把自己放在一个移动的沙地上,今天能编过,明天可能就编不过了。建议锁定一个具体的标签版本,比如v1.14.0。
3. 核心配置细节与实操要点
3.1 最小化CMake配置逐行拆解
我先把一份能直接用的CMakeLists.txt贴出来,然后逐段解释关键点的含义。这个配置是我在多平台验证过的,Windows(VS2019/2022)、Linux(GCC/Clang)、macOS都能正常工作。
cmake_minimum_required(VERSION 3.14) project(CppDemoProject CXX) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG v1.14.0 GIT_SHALLOW TRUE ) set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) include(CTest) add_library(core_math STATIC src/math_utils.cpp ) target_include_directories(core_math PUBLIC include) add_executable(test_core_math tests/test_math_utils.cpp ) target_link_libraries(test_core_math PRIVATE core_math GTest::gtest GTest::gtest_main ) include(GoogleTest) gtest_discover_tests(test_core_math)这份配置的核心点有这么几个:
第一,GIT_SHALLOW TRUE是给FetchContent用的,它会让git clone只拉取最新一层的提交历史,而不是拉全量历史。对于GoogleTest这种仓库来说,体积差很多,配置速度明显更快。
第二,BUILD_GMOCK OFF和INSTALL_GTEST OFF这两个变量我建议必须设置。如果你不需要GoogleMock(GoogleTest的mock框架),关闭它可以省掉很多不必要的编译时间。INSTALL_GTEST OFF则是防止GoogleTest把自己的库装到系统目录,避免污染环境。
第三,GTest::gtest和GTest::gtest_main是两个导入目标。前者是GoogleTest核心库,后者提供了一个已经实现好的main函数。如果你不需要自定义main函数,直接链接GTest::gtest_main是最省事的做法,它会帮你把测试框架初始化好。
3.2 include路径和链接目标的选择陷阱
这里要单独提醒一下:网上很多老教程会让你在target_link_libraries里写gtest而不是GTest::gtest,这是有本质区别的。小写的gtest是在用传统变量方式引用库名,虽然大多数情况能链上,但它不携带公共头文件目录、编译选项这些传递依赖信息;大写带命名空间的GTest::gtest是CMake的导入目标(imported target),它会自动把GoogleTest的头文件路径和必要的编译定义带给你。
用导入目标的最大好处是,你不需要自己手动写target_include_directories(test_core_math PRIVATE ${GTEST_INCLUDE_DIRS})这种代码,少掉一堆环境相关的脆弱配置。
另外,链接GTest::gtest_main之后,你自己写的测试文件里就不要再自定义main函数了,否则会有链接冲突。如果确实需要自己写main(比如要在跑测试前初始化日志系统),那就只链GTest::gtest,然后在测试文件里自己实现main函数,类似这样:
#include "gtest/gtest.h" int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); // 这里可以加你自己的初始化逻辑,比如日志配置 return RUN_ALL_TESTS(); }这里有个非常容易踩的坑:如果你同时链接了gtest_main又自己定义了main,链接器通常会报重复定义的错误,但某些编译器(比如MSVC)下报错信息会非常不直观,看上去像是标准库的内部冲突。遇到这种报错,第一反应就该检查是不是main函数冲突了。
3.3 测试文件目录组织与命名规范
测试代码的目录结构,我建议跟源码目录分开,这是从"测试代码也是要维护的代码"这个角度出发的。一个清晰的组织方式大概是这样的:
project_root/ ├── CMakeLists.txt ├── include/ │ └── math_utils.h ├── src/ │ └── math_utils.cpp └── tests/ ├── CMakeLists.txt ├── test_math_utils.cpp └── test_string_utils.cpp在tests目录下单独放一个CMakeLists.txt,然后在根目录里用add_subdirectory(tests)引入,这是目前最主流的做法。好处是测试代码的构建逻辑和主工程解耦,将来加新测试文件只改一行。如果测试文件特别多,还可以按模块分子目录,每个模块一个测试子目录,规则统一。
关于命名规范,我一般在源文件和工作区的命名上遵守"源文件名对应测试文件名"的原则。比如math_utils.cpp对应的测试文件就叫test_math_utils.cpp,这样在IDE里一眼就能找到对应关系,也方便CI配置按文件路径筛选测试。
需要注意的一点是,测试二进制名的命名也有章法。我通常用test_模块名这种格式,比如test_core_math。这样ctest -R test_core_math之类的筛选命令用起来很顺手,不会出现测试二进制名和测试用例名混在一起的情况。
4. 实操过程与完整接入示例
4.1 一个完整的业务代码+测试代码示例
命令行工具也得有东西测,我先写一个简单的工具类作为被测对象。假设我们有一个数学工具函数库,里面有个整数除法函数,它要处理除零的情况,返回一个错误码或合理默认值。这个函数的实现可能长这样:
// include/math_utils.h #pragma once namespace core_math { // 整数除法,返回是否成功,result存放除法结果 // divisor为0时返回false,result置0 bool safe_divide(int dividend, int divisor, int* result); }// src/math_utils.cpp #include "math_utils.h" namespace core_math { bool safe_divide(int dividend, int divisor, int* result) { if (divisor == 0) { if (result != nullptr) { *result = 0; } return false; } if (result != nullptr) { *result = dividend / divisor; } return true; } }这种函数就很适合写单元测试:正常输入、边界输入(负数、除零、空指针)都在一个函数里,逻辑简单但分支不少。拿这个当例子,正好能把GoogleTest的断言、死亡测试、参数化测试都串起来。
对应的测试文件可以拆成几个测试用例组:
// tests/test_math_utils.cpp #include "math_utils.h" #include "gtest/gtest.h" namespace { using core_math::safe_divide; // 正常除法场景 TEST(SafeDivideTest, NormalDivision) { int result = 0; EXPECT_TRUE(safe_divide(10, 2, &result)); EXPECT_EQ(result, 5); } // 除零场景 TEST(SafeDivideTest, DivideByZero) { int result = 100; EXPECT_FALSE(safe_divide(10, 0, &result)); EXPECT_EQ(result, 0); } // 负数除法场景 TEST(SafeDivideTest, NegativeDivision) { int result = 0; EXPECT_TRUE(safe_divide(-10, 2, &result)); EXPECT_EQ(result, -5); } // 空指针场景(不崩溃即可) TEST(SafeDivideTest, NullResultPointer) { EXPECT_FALSE(safe_divide(10, 0, nullptr)); EXPECT_TRUE(safe_divide(10, 2, nullptr)); } }这里EXPECT_TRUE和EXPECT_EQ是GoogleTest里最常见的断言宏。注意EXPECT_*系列失败不会终止当前测试用例,ASSERT_*系列失败会直接跳过后续语句。在需要"如果前面失败,后面就没意义"的场景(比如检查空指针后再解引用)必须用ASSERT_*,否则会崩掉。
4.2 参数化测试的引入时机与写法
当测试场景的入参特别多的时候,比如要覆盖十几个不同的除法组合,一个个写TEST会非常啰嗦。GoogleTest提供了参数化测试机制,把数据驱动的用例统一管理起来。
class SafeDivideParameterizedTest : public ::testing::TestWithParam<std::tuple<int, int, int, bool>> { protected: void SetUp() override {} void TearDown() override {} }; TEST_P(SafeDivideParameterizedTest, CheckDivisionResult) { auto params = GetParam(); int dividend = std::get<0>(params); int divisor = std::get<1>(params); int expected_result = std::get<2>(params); bool expected_success = std::get<3>(params); int result = 0; bool success = safe_divide(dividend, divisor, &result); EXPECT_EQ(success, expected_success); if (expected_success) { EXPECT_EQ(result, expected_result); } } INSTANTIATE_TEST_SUITE_P( SafeDivideParameterizedGroup, SafeDivideParameterizedTest, ::testing::Values( std::make_tuple(10, 2, 5, true), std::make_tuple(0, 2, 0, true), std::make_tuple(-10, 2, -5, true), std::make_tuple(10, -2, -5, true), std::make_tuple(10, 0, 0, false), std::make_tuple(-10, 0, 0, false) ) );参数化测试有个细节:INSTANTIATE_TEST_SUITE_P必须在全局命名空间或者包含测试类的命名空间内调用,不能放在函数体里。另外,参数化用例的名字会包含参数索引,比如SafeDivideParameterizedGroup/SafeDivideParameterizedTest.CheckDivisionResult/0这样的形式,在ctest里会以这个完整名字显示。
不过参数化测试也容易写过头。如果参数一多,测试可读性会急剧下降,别人维护时看半天不知道每个参数是干嘛的。我一般只在参数组合超过三组的时候才用参数化,不然老老实实写多个TEST反而更直观。
4.3 构建、编译与运行全流程
配置好CMakeLists之后,实际构建过程就是标准的CMake流程:
mkdir -p build cd build cmake .. cmake --build . --parallel 4 ctest --output-on-failure如果你在Windows上用Visual Studio,cmake --build . --config Release --parallel 4能指定构建配置。生成的是ALL_BUILD工程,要单独跑测试二进制也有,但直接用ctest -C Release能省掉找路径的麻烦。
首次执行cmake ..时,FetchContent会去GitHub拉代码,这一步耗时取决于网络环境,一般一两分钟。拉完之后会看到CMake输出里多出一段关于googletest的配置信息,不必手动干预。
如果跑ctest之后发现测试不通过,输出会是类似这样:
[==========] Running 4 tests from 1 test suite. [----------] Global test environment set-up. [----------] 4 tests from SafeDivideTest [ RUN ] SafeDivideTest.NegativeDivision [ OK ] SafeDivideTest.NegativeDivision (0 ms) ... [ FAILED ] SafeDivideTest.DivideByZero (0 ms) ...ctest --output-on-failure会把失败用例的详细输出打印出来,否则默认只是报"测试失败"而不展示具体断言信息,排查问题极其不方便。这个参数我建议每次都带上,或者加进CMake的CTest配置里。
4.4 老项目接入时的渐进式迁移策略
如果你的项目是已经跑了好几年的老工程,几个G的代码量,测试覆盖率基本为零,想一口气把GoogleTest全接上是不现实的。我的经验是走一条渐进路线:
第一阶段,先把编译系统和GoogleTest接好,不加任何测试。让工程能带着GoogleTest正常编译出测试空架子。这个阶段的目标是打通"链路",验证CMake配置在目标平台可行。
第二阶段,挑一个逻辑最独立、改动最少的模块(比如工具函数、字符串处理、容器封装),给这个模块的核心函数补测试。这个阶段主要练手,同时也验证团队里其他成员对测试框架的接受度。
第三阶段,在测试覆盖率工具配合下,按模块重要性逐块补测试。接入了这个阶段之后,再把测试配置推进CI流程里。
这个渐进策略核心是不折腾。我见过有人一上来就要求全部模块补测试,结果三个月后测试代码还没影,原因是团队直接被任务量吓跑了。饭一口一口吃,测试覆盖率也是从百分之零一点点爬上去的。
5. 常见问题与排查技巧实录
5.1 FetchContent拉取失败或超时
最典型的错误是:
CMake Error at CMakeLists.txt:xx (FetchContent_MakeAvailable): Failed to download "googletest"这个原因战基本情况是网络问题。解决方案有三种:
第一种,启用GIT_SHALLOW,缩短拉取量,这个前面已经提到。
第二种,给git配置代理或镜像。如果你的开发环境有稳定的内网镜像源,可以在FetchContent_Declare里把GIT_REPOSITORY换成内网镜像地址。这个方式在几个公司内网环境里实测很稳定。
第三种,不用FetchContent,改成把GoogleTest源码下载好之后直接放到third_party/googletest目录下,然后改用add_subdirectory(third_party/googletest)方案。这个方式牺牲了"自动下载"的便利,但胜在完全可控、离线可用。
提示:在公司内网环境里,我强烈建议事先把GoogleTest源码包放到内部代码仓库或镜像站点,然后让
FetchContent的URL直接指向内部地址,既省外网带宽又能保证版本一致性。
5.2 编译报错和C++标准不匹配
编译测试代码时报的一堆模板错误,有相当概率是C++标准不匹配导致的。常见报错有static_assert failed: gtest requires c++14 or higher,或者error: 'result_of' is not a member of 'std'这类。
解决方式是在FetchContent_MakeAvailable(googletest)之前,给GoogleTest单独指定标准:
set(gtest_cxx_standard 14 CACHE INTERNAL "")或者更直接一点,在FetchContent_MakeAvailable之后,手动把GoogleTest目标的标准属性覆盖掉。无论哪种方式,关键是让GoogleTest的编译标准和编译器支持能力对齐。
如果项目本身用C++17或C++20,一般不用管这个;但如果项目还在C++11上,你又有心用新版GoogleTest,那就要仔细设置上面这段配置。
5.3 MSVC环境下的一些特殊问题
Windows + Visual Studio环境下遇到的问题往往会更"诡异"一些。我碰到过几个比较有代表性的:
一个是/MD和/MT运行库混用导致的链接错误。如果你的主工程用的是静态运行库(/MT),而GoogleTest默认拉下来编译时用的是动态运行库(/MD),链接阶段会报一堆LNK2038 Mismatch detected for RuntimeLibrary错误。解决办法是保证全局运行库选项一致,或者在顶层设置:
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")让主项目和GoogleTest统一用同一种运行库。
另一个是Debug和Release版本的pdb文件冲突。FetchContent默认会同时编译Debug和Release配置,如果两个配置的中间目录没有正确隔离,偶尔会报pdb写入冲突。一般让CMake的CMAKE_CONFIGURATION_TYPES保持默认就行,实在不行就在配置时固定只生成一种配置,比如cmake -DCMAKE_CONFIGURATION_TYPES=Release ..。
还有Windows上路径过长导致的问题。GoogleTest源码树如果被放在一个很深的目录里,某些文件路径会超过260字符限制,导致编译失败。解决办法是把整个工程的根目录放在浅层路径下,比如C:\work\cpp_demo\而不是C:\Users\张三\Documents\Project\ComplexName\...。
5.4 测试数量异常或CTest发现不到用例
有次我跑ctest,发现用例数量比预期少了很多。排查下来发现是没有调用gtest_discover_tests,只用了add_test注册整个测试二进制。这时CTest只会把整个可执行文件当成一个测试项,等于丢失了用例粒度。
正确的做法是include(GoogleTest)之后调用gtest_discover_tests(test_core_math),它会运行目标程序,自动解析出所有测试用例名,逐个注册到CTest。需要提醒的是,gtest_discover_tests是在构建之后,通过动态执行测试二进制来发现用例的,所以如果测试二进制本身跑不起来,发现用例这步也会失败。
另一个容易忽略的细节是:gtest_discover_tests在交叉编译场景下没法正常工作,因为目标平台的测试二进制无法在构建机上运行。如果你的工程在交叉编译,那得手动add_test并指定测试命令,或者用gtest_discover_tests的DISCOVERY_MODE PRE_TEST在目标平台运行时发现用例。
5.5 测试覆盖率统计的接入
接入GoogleTest之后,很多人紧接着就想看覆盖率。Linux下用gcov/lcov配--coverage编译选项,Windows下用OpenCppCoverage或Visual Studio的覆盖率工具。CMake里大致这么配:
target_compile_options(test_core_math PRIVATE --coverage) target_link_options(test_core_math PRIVATE --coverage)跑完测试后,用lcov --capture --directory . --output-file coverage.info生成覆盖率数据文件,再用genhtml转成HTML报告。我个人建议覆盖率统计单独加一个CMake选项,比如BUILD_WITH_COVERAGE,默认关闭,需要看覆盖率的时候才打开,免得平时编译带上一堆覆盖率的插桩影响性能。
覆盖率这个指标需要辩证看。代码覆盖率80%跟100%的项目,测试成本差异是很大的,但多数人的核心模块根本没那么多分支需要全覆盖。初期先把工具链跑通,主流程覆盖到位,比追求一个好看的数字要实在得多。
6. 实战项目中的二次封装与团队协作
6.1 编写公共测试基础设施
当测试文件多起来之后,你会发现很多初始化代码是重复的。比如每个测试模块都要初始化日志系统,都要设置某些环境变量。这时候可以考虑在tests目录下放一个公共的测试基类或公共main函数。
比如我们可以写一个TestEnvHelper,在main里统一做环境准备:
// tests/test_main.cpp #include "gtest/gtest.h" int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); // 初始化日志系统 // 设置测试环境变量 // 创建测试临时目录 return RUN_ALL_TESTS(); }这个文件编译成一个静态库,然后所有测试二进制都链接它。需要说明的是,链接了带main的库之后,所有测试文件里就不能再出现main函数了,这正好是在团队里统一规范的机会。
另外,我还会把常用的断言封装成辅助函数或宏。比如判断两个浮点数数组是否近似相等:
void EXPECT_VECTOR_NEAR(const std::vector<float>& lhs, const std::vector<float>& rhs, float tolerance) { ASSERT_EQ(lhs.size(), rhs.size()); for (size_t i = 0; i < lhs.size(); ++i) { EXPECT_NEAR(lhs[i], rhs[i], tolerance); } }这种辅助工具能让测试代码简洁不少,也避免了在每个测试文件里重复实现一遍同样的逻辑。
6.2 CI流水线里的集成建议
在CI里跑测试,重点不是把测试跑起来,而是让失败信号清晰可操作。我建议CI脚本这么组织:
第一步,构建测试二进制。第二步,跑ctest --output-on-failure --parallel 4。第三步,如果跑的是覆盖率模式,把HTML报告上传到内部制品库或文档站。第四步,把失败的测试输出归档成附件,方便人去看。
有一个细节值得提:在CI里跑ctest -R做用例筛选时,正则表达式匹配的是测试全名,包括套件名和用例名。比如只想跑SafeDivideTest这个套件的用例,可以ctest -R "SafeDivideTest\.",点号要记得转义,不然会匹配更多用例。
6.3 团队规范:PR门禁与测试要求
引入GoogleTest之后,团队协作的约束也会自然形成。我的经验是,在PR门禁里至少强制两条:新增修改的源码文件必须有对应的测试文件,覆盖率不能低于改动前的水平。这两条不一定要写死在CI脚本里,但作为代码评审的检查项,定下规矩之后,测试代码的质量会明显提升。
实际落地时,可以给代码仓库配一个简单的插件脚本,扫描本次改动涉及的源文件,检查同名测试文件是否同时出现在改动列表里。这样比人在评审时一条条翻文件要高效得多。
7. 踩坑记录与个人体会
GoogleTest集成这事本身不复杂,但要把体验做顺,确实有不少隐藏成本。最后把我在实际项目中积累的几点体会总结一下。
第一点,关于测试二进制和被测代码的链接关系。受保护成员的测试涉及另一个话题:FRIEND_TEST宏。GoogleTest提供FRIEND_TEST允许测试代码访问被测类的私有成员,但需要在被测类里声明友元。这个功能慎用,因为它把测试代码和被测类的内部实现耦合了,一旦被测类重构,测试代码跟着大改。我一般只在白盒测试关键算法时使用,日常还是优先测公开接口。
第二点,关于测试运行时间。GoogleTest跑起来很快,但如果你在测试里依赖文件IO、网络请求或者sleep等待,测试时长会一路飙升。我见过一个项目的测试套件因为每个用例都sleep 100毫秒,跑完整个套件要20分钟,消极影响非常明显。建议对涉及IO的用例统一用"临时目录+快速读写"策略,对网络请求一律mock掉,不要在测试里碰真实网络。
第三点,关于断言级别的选择。EXPECT和ASSERT混用是个技术活,我自己的原则是:如果后续代码依赖于前一步的结果才能继续,用ASSERT;如果后续代码即使失败也能安全继续,用EXPECT。比如ASSERT_FALSE(safe_divide(10, 0, &result))失败后,result值就没有意义了,继续往下跑只会产生误导性信息,这种场景应该用ASSERT让用例尽早失败。
第四点,也是我最有体会的一点:测试代码和业务代码一样需要评审。很多团队对测试代码的态度是"能跑就行",结果测试里藏着各种魔数、重复逻辑、深拷贝大对象,维护成本一点都不比业务代码低。请把测试代码当作生产代码来写,它也是资产,不是附属品。
C++项目接入GoogleTest只是第一步,真正值钱的是让测试这件事变成每天开发流程里自然的一部分。我自己的体会是,接入框架的技术门槛其实很低,真正难的是坚持写下去,让测试真正帮团队把改代码的手速和信心提上来。这套CMake接入方案和配套规范,我之后在几个项目里持续打磨过,回头再看依然是最顺手的一套操作方式。