- 数据库客户端
- 桌面应用
【免费下载链接】robomongo
Native cross-platform MongoDB management tool
Google Test(googletest)是 Google 开源的 C++ 测试框架,本仓库将其 1.8.1 版本以第三方依赖的形式完整内置于src/third-party/googletest-1.8.1/,并通过 CMake 接入 Robomongo(Robo 3T 的原生跨平台 MongoDB 管理工具)的构建体系。本文以该框架的 README 文档为主体,结合仓库内的构建脚本与真实测试用例,讲解 googletest 的核心特性、平台要求、三种主流构建方式,以及如何像 Robomongo 一样用它为 C++ 代码编写并运行单元测试。
Google Test 是什么:Google 的 C++ 测试框架
src/third-party/googletest-1.8.1/README.md开篇即点明:本仓库是原先彼此独立的GoogleTest与GoogleMock两个项目的合并产物——两者关系过于紧密,因此合并维护、一并发布。GoogleTest 是核心测试框架,GoogleMock 则是其扩展,用于编写和使用 C++ mock 类,其独立文档见 googlemock/README.md。
该 README 还披露了框架版本演进路线(属于项目自述,非当前仓库承诺):
- 1.8.x:将是最后一个支持 pre-C++11 编译器的版本,此后不再接受新功能请求,仅受理被证明"关键"的 bug 修复;
- 1.9.x:1.8.x 之后的工作重点是清理技术债务并打上 1.9.x 标签;
- 1.9.x 之后:googletest 将遵循 Abseil 的 "Live at Head" 哲学。
本仓库固定使用的是1.8.1版本,在根 CMakeLists.txt 中通过set(GOOGLE_TEST_VERSION 1.8.1)显式声明,并对应set(GOOGLE_TEST_DIR src/third-party/googletest-${GOOGLE_TEST_VERSION})、add_subdirectory(${GOOGLE_TEST_DIR})纳入构建,版本号还会作为编译期宏GOOGLE_TEST_VERSION注入到主程序(见 src/robomongo/CMakeLists.txt)。
核心功能特性一览
README 归纳了 googletest 的能力清单,这些特性也正是 Robomongo 用它组织测试的基础:
- xUnit 测试框架:与 JUnit/PyUnit 同构的经典架构;
- 测试发现(Test discovery):
TEST()/TEST_F()注册的测试会被框架自动发现,无需手工枚举; - 丰富的断言(Assertions):覆盖布尔、数值比较、字符串比较等场景;
- 用户自定义断言:可扩展框架自身的断言体系;
- 死亡测试(Death tests):用于验证进程崩溃类行为;
- 致命与非致命失败:
ASSERT_*与EXPECT_*两套失败语义; - 值参数化测试(Value-parameterized tests):同一测试逻辑跑多组输入;
- 类型参数化测试(Type-parameterized tests):同一测试逻辑跑多种类型;
- 多种运行选项:通过命令行 flag 控制测试程序行为;
- XML 测试报告生成:输出机器可读的测试结果。
支持的平台与构建要求
README 明确列出 googletest 已实际使用的平台:Linux、Mac OS X、Windows、Cygwin、MinGW、Windows Mobile、Symbian。当前官方积极支持的构建环境为 Linux、Windows、Mac OS X 与 Cygwin,其他平台(如 Solaris、AIX、z/OS)属于尽力而为。
各平台的最小构建要求如下:
| 平台 | 要求 |
|---|---|
| Linux | GNU 兼容的 make/gmake、POSIX 标准 shell、POSIX(-2) 正则库(regex.h)、C++98 标准编译器 |
| Windows | Microsoft Visual C++ 2015 或更新版本 |
| Cygwin | Cygwin v1.5.25-14 或更新版本 |
| Mac OS X | Mac OS X v10.4 Tiger 或更新版本、Xcode Developer Tools |
需要注意的是,框架本身的"最低要求"(C++98)与 Robomongo 主工程并不相同——主工程已在根 CMakeLists.txt 中启用CMAKE_CXX_STANDARD 17,因此实际编译测试代码时遵循的是 C++17 标准。
在 Robomongo 中的集成:CMake 与目录结构
googletest 1.8.1 在仓库中的目录布局为:
src/third-party/googletest-1.8.1/ ├── googletest/ # 核心测试框架(含 include、src、docs、make、msvc、xcode 等) ├── googlemock/ # mock 扩展(含 include、src、docs、scripts 等) ├── CMakeLists.txt ├── CONTRIBUTING.md └── README.mdREADME 指出,更详细的构建说明在其内部文档 googletest/README.md 中;入门概念则见 googletest/docs/primer.md。下文三种构建方式均出自这份内部构建文档。
方式一:通用手工构建(Generic Build)
假设 googletest 位于${GTEST_DIR},把src/gtest-all.cc编译进库目标即可——${GTEST_DIR}/include放入系统头文件搜索路径,${GTEST_DIR}放入普通头文件搜索路径。Linux/gcc 下等价于:
g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ -pthread -c ${GTEST_DIR}/src/gtest-all.cc ar -rv libgtest.a gtest-all.o-pthread不可省略,因为 googletest 内部使用了线程。随后编译自己的测试源文件并链接 gtest:
g++ -isystem ${GTEST_DIR}/include -pthread path/to/your_test.cc libgtest.a \ -o your_test对应的源文件正是本仓库中的 googletest/src/gtest-all.cc。make/目录还附带一个基于 GNU make 的 Makefile,在默认环境匹配时,make && ./sample1_unittest即可产出库与示例测试。
方式二:独立 CMake 工程构建
googletest 自带跨平台 CMake 脚本("C" 即 cross-platform),可生成你所在平台的本地构建文件:
mkdir mybuild # 创建构建输出目录 cd mybuild cmake ${GTEST_DIR} # 生成原生构建脚本如需连框架自带的 samples 一起构建,将最后一条命令替换为cmake -Dgtest_build_samples=ON ${GTEST_DIR}。随后:类 Unix 系统下出现 Makefile,make即可;Windows + Visual Studio 下生成gtest.sln与若干.vcproj;Mac OS X + Xcode 下生成.xcodeproj。
方式三:并入既有 CMake 工程(Robomongo 采用的方式)
对于一个已经使用 CMake 的工程,更健壮的做法是直接作为主工程的一部分构建 gtest——这样 gtest 与主工程共用同一套编译器与链接器设置,可规避 debug/release 等不兼容库引发的问题,在 Windows 上尤其有用。googletest/README.md 给出了四种让源码进入主构建的途径:手工下载放到固定位置、直接内嵌进主工程源码树、以 git submodule 引入、在 CMake configure 阶段联网下载。
其中"configure 阶段下载"的实现要点是:用一个单独的CMakeLists.txt.in(cmake_minimum_required(VERSION 2.8.2),通过ExternalProject_Add定义 googletest 的下载与构建元数据),在主工程里依次执行configure_file、execute_process(COMMAND ${CMAKE_COMMAND} -G ...)、execute_process(COMMAND ${CMAKE_COMMAND} --build .),最后用add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src ${CMAKE_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL)拉入构建,并链接gtest/gtest_main目标。此外文档还提醒:若 CMake 版本低于 2.8.11,需要手工include_directories("${gtest_SOURCE_DIR}/include"),因为头文件搜索路径的自动传递是从 2.8.11 才开始支持的。
本仓库实际选择了"源码内嵌 +add_subdirectory"的路线:根 CMakeLists.txt 依次add_subdirectory各第三方库后,再add_subdirectory(src/robomongo)与add_subdirectory(src/robomongo-unit-tests)。
补充:Visual Studio 运行库(Runtime Library)不匹配
googletest/README.md 专门警告了一个 Windows 常见坑:新版 VS 工程默认动态链接 C 运行库,而 gtest 默认静态链接,会报LNK2038: mismatch detected for 'RuntimeLibrary'。框架为此提供 CMake 选项gtest_force_shared_crt,开启后 gtest 也动态链接运行库,从而与宿主工程匹配。Robomongo 在 src/robomongo-unit-tests/CMakeLists.txt 中即设置了set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)。
Robomongo 单元测试工程实战
Robomongo 的单元测试工程位于src/robomongo-unit-tests/,其构建脚本与目录 README 共同定义了测试的组织规范。
测试文件命名约定
src/robomongo-unit-tests/README.md 说明:该目录本身只是 CMake 的占位符,测试源文件与被测文件同目录存放、并肩出现,命名形如:
RoboCrypt.cpp # 被测源码 RoboCrypt_test.cpp # 对应测试 StringOperations.cpp StringOperations_test.cpp即测试文件放在src/robomongo/及其子目录中,文件名以_test.cpp结尾。cmake/的 RobomongoCommon.cmake 等模块与src/robomongo-unit-tests/CMakeLists.txt中的注释共同印证了这一约定:"New test & source file pairs MUST have the following format:Test file: /path/Foo_test.cpp、Source file: /path/Foo.cpp or /path/Foo.h"。
单元测试的 CMake 配置拆解
src/robomongo-unit-tests/CMakeLists.txt 完整展示了 googletest 的接入方式:
- Linux 特例:脚本开头
if (SYSTEM_LINUX) ... return() endif(),明确注明"Currently unit testing is disabled for Linux due to MongoDB linking problems"(因 MongoDB 链接问题,Linux 上当前禁用单元测试)。因此robo_unit_tests目标实际在 macOS 与 Windows 上构建; - 启用 gtest:
enable_testing()、include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}),并强制gtest_force_shared_crt; - 登记测试源:
SOURCES_TEST依次列出 RoboCrypt_test.cpp、StringOperations_test.cpp、HexUtils_test.cpp; - 生成可执行目标:
add_executable(robo_unit_tests ${SOURCES_TEST}),并add_dependencies(robo_unit_tests robomongo)保证主目标先构建; - 复用主程序目标文件:通过
get_target_property(ROBO_SOURCES robomongo SOURCES)取出 robomongo 的全部源码,过滤掉main.cpp后,按平台拼出对应.obj/.cpp.o路径,作为ROBO_OBJ_FILES链入测试程序——这是"直接测试真实业务代码、不做桩"的关键设计; - 链接依赖:
target_link_libraries(robo_unit_tests gtest gtest_main Qt5::Widgets Qt5::Network Qt5::Xml qjson qscintilla mongodb ssh Threads::Threads ${ROBO_OBJ_FILES})——注意同时链入了gtest与gtest_main,后者提供现成的main(); - Windows 运行环境:把
robo_unit_tests所需的 Qt5 DLL 与 OpenSSL 的libssl-1_1-x64.dll、libcrypto-1_1-x64.dll拷贝到测试二进制目录,保证可独立运行。
macOS 分支还会额外链接Security、CoreFoundation与-lresolv(见脚本第 57-60 行)。
三个真实测试用例解读
字符串工具——StringOperations_test.cpp 是最简洁的TEST()用例,文件头注释还给出了本工程的命名风格建议(UnitOfWorkName_ScenarioUnderTest_ExpectedBehavior):
#include "gtest/gtest.h" #include "robomongo/utils/StringOperations.h" TEST(StringOperationsTests, captilizeFirstChar) { // EXPECT_EQ("Abcc", Robomongo::captilizeFirstChar("abc")); // Simulating failing test EXPECT_EQ("Abc", Robomongo::captilizeFirstChar("abc")); // Simulating passing test }被测函数Robomongo::captilizeFirstChar声明于 src/robomongo/utils/StringOperations.h。
加解密往返——RoboCrypt_test.cpp 对密码存储模块 RoboCrypt.h 做加密-解密往返校验,覆盖了含转义、标点、符号的多种密码形态,EXPECT_EQ(pwd, decryptedPwd)验证一致性:
TEST(RoboCrypt_CoreTests, encrypt_decrypt) { auto const pwds = { "Tyu_aBq", "_?asdfghjkl;'piop[.,/", ".?/`_@~!#$%^^&&*)_)_+=-", "<>?/.,;':][p{}|\"" }; for (auto const& pwd : pwds) { const std::string encryptedPwd = Robomongo::RoboCrypt::encrypt(pwd); const std::string decryptedPwd = Robomongo::RoboCrypt::decrypt(encryptedPwd); EXPECT_EQ(pwd, decryptedPwd); } }十六进制判定——HexUtils_test.cpp 用EXPECT_TRUE(Robomongo::HexUtils::isHexString("a"));验证工具函数的布尔返回值。
这三个文件清晰展示了 googletest 在真实项目中的典型形态:#include "gtest/gtest.h"+TEST(TestCaseName, TestName)+ 断言宏,无需自写main()(由gtest_main提供)。
编写测试的核心 API(来自 Primer)
googletest/docs/primer.md 是官方入门文档,下面提炼其核心内容,与仓库测试用例一一对应。
断言:ASSERT_* 与 EXPECT_*
断言是检查条件是否为真的语句,结果分为成功、非致命失败、致命失败三种;致命失败会中止当前函数。断言成对出现:ASSERT_*失败即致命并中止当前函数,EXPECT_*失败为非致命、不中止函数。惯例是优先EXPECT_*(一次运行能报出多个失败),只有"继续运行没有意义"时才用ASSERT_*。
自定义失败消息通过<<流入宏:
ASSERT_EQ(x.size(), y.size()) << "Vectors x and y are of unequal length"; for (int i = 0; i < x.size(); ++i) { EXPECT_EQ(x[i], y[i]) << "Vectors x and y differ at index " << i; }基本断言(Linux、Windows、Mac 均可用):
| 致命断言 | 非致命断言 | 验证内容 |
|---|---|---|
ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition 为真 |
ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition 为假 |
二元比较断言:
| 致命断言 | 非致命断言 | 验证内容 |
|---|---|---|
ASSERT_EQ(val1, val2); | EXPECT_EQ(val1, val2); | val1 == val2 |
ASSERT_NE(val1, val2); | EXPECT_NE(val1, val2); | val1 != val2 |
ASSERT_LT(val1, val2); | EXPECT_LT(val1, val2); | val1 < val2 |
ASSERT_LE(val1, val2); | EXPECT_LE(val1, val2); | val1 <= val2 |
ASSERT_GT(val1, val2); | EXPECT_GT(val1, val2); | val1 > val2 |
ASSERT_GE(val1, val2); | EXPECT_GE(val1, val2); | val1 >= val2 |
注意事项:ASSERT_EQ对指针做的是指针相等判断,比较两个 C 字符串是否同内容应改用ASSERT_STREQ;指针与空值比较推荐nullptr(ASSERT_EQ(ptr, nullptr));实参只求值一次,因此允许带副作用,但求值顺序不保证;浮点比较建议使用 advanced 指南中的浮点专用变体。
C 字符串比较断言:
| 致命断言 | 非致命断言 | 验证内容 |
|---|---|---|
ASSERT_STREQ(str1, str2); | EXPECT_STREQ(str1, str2); | 两 C 字符串内容相同 |
ASSERT_STRNE(str1, str2); | EXPECT_STRNE(str1, str2); | 两 C 字符串内容不同 |
ASSERT_STRCASEEQ(str1, str2); | EXPECT_STRCASEEQ(str1, str2); | 忽略大小写后内容相同 |
ASSERT_STRCASENE(str1, str2); | EXPECT_STRCASENE(str1, str2); | 忽略大小写后内容不同 |
注意"NULL 指针与空字符串被视为不同";比较std::string对象请用EXPECT_EQ系列而非*STREQ*。
简单测试:TEST() 宏
创建测试三步走:用TEST()定义命名测试函数(普通无返回值 C++ 函数)→ 函数体内用断言检查 → 任一断言失败或崩溃则整个测试失败。宏签名为TEST(TestCaseName, TestName),参数从一般到具体,两个名字都必须是合法 C++ 标识符且不应包含下划线。官方给出的阶乘示例:
TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(Factorial(0), 1); } TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(Factorial(1), 1); EXPECT_EQ(Factorial(2), 2); EXPECT_EQ(Factorial(3), 6); EXPECT_EQ(Factorial(8), 40320); }同一测试用例(第一个参数相同)的测试在结果报告中归组展示。Robomongo 的TEST(StringOperationsTests, captilizeFirstChar)与TEST(RoboCrypt_CoreTests, encrypt_decrypt)正是该模式的应用。
测试夹具(Test Fixtures):TEST_F() 宏
当多个测试共享同一份对象配置时,使用夹具类:从::testing::Test派生,protected:起始,声明成员对象;需要时实现默认构造函数或SetUp()(注意拼写,C++11 下用override校验)、析构函数或TearDown()。使用TEST_F(TestCaseName, TestName),此时第一个参数必须是夹具类名(_F即 Fixture)。
官方以 FIFO 队列Queue为例:
class QueueTest : public ::testing::Test { protected: void SetUp() override { q1_.Enqueue(1); q2_.Enqueue(2); q2_.Enqueue(3); } Queue<int> q0_; Queue<int> q1_; Queue<int> q2_; }; TEST_F(QueueTest, IsEmptyInitially) { EXPECT_EQ(q0_.size(), 0); } TEST_F(QueueTest, DequeueWorks) { int* n = q0_.Dequeue(); EXPECT_EQ(n, nullptr); n = q1_.Dequeue(); ASSERT_NE(n, nullptr); // 后续要解引用,必须用 ASSERT_ EXPECT_EQ(*n, 1); ... }关键机制:每个TEST_F测试运行时,googletest 都会新建一个全新的夹具对象,依次执行构造 →SetUp()→ 测试体 →TearDown()→ 析构,且不同测试之间绝不复用同一夹具对象,一个测试对夹具的改动不会影响其他测试。此例也示范了ASSERT_*的正确使用场景:ASSERT_NE(n, nullptr)之后要解引用n,若为 NULL 将段错误。
运行测试与 main() 函数
TEST()/TEST_F()会自动向框架注册测试,无需手动罗列。调用RUN_ALL_TESTS()即可运行链接单元内的全部测试(可跨文件、跨测试用例),全部成功返回 0,否则返回 1。流程为:保存 gtest flags 状态 → 对每个测试构造夹具、SetUp()、运行、TearDown()、析构 → 恢复 flags 状态。
两条强制规则:
- 绝不能忽略
RUN_ALL_TESTS()的返回值(否则编译报错)——自动化测试服务依据的是进程退出码,因此main()必须return RUN_ALL_TESTS();; - 只能调用一次——多次调用会与死亡测试等高级特性冲突,不受支持。
编写自定义main()的标准骨架:
#include "gtest/gtest.h" int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }::testing::InitGoogleTest()负责解析命令行中的 gtest flags 并从参数中移除它们,必须在RUN_ALL_TESTS()之前调用(Windows 的 UNICODE 模式下亦支持宽字符串)。如果不想写main(),直接链接gtest_main库即可——Robomongo 的robo_unit_tests正是同时链接gtest与gtest_main的做法。
常用配置宏:按需微调 googletest
googletest/README.md 指出,框架默认配置在特殊环境下可能不适用,可用编译命令行定义GTEST_XYZ类控制宏(赋 1 或 0)来开关特性。完整清单见 include/gtest/internal/gtest-port.h,最常用的有:
| 宏 | 作用 |
|---|---|
-DGTEST_USE_OWN_TR1_TUPLE=0 | 项目已用 TR1 tuple 时,让 gtest 使用同一实现,避免两套 tuple 冲突 |
-DGTEST_USE_OWN_TR1_TUPLE=1 | 强制 gtest 使用自带的 TR1 tuple 子集实现 |
-DGTEST_HAS_TR1_TUPLE=0 | 完全禁用 tuple 及依赖 tuple 的特性 |
-DGTEST_HAS_PTHREAD=1/=0 | pthread 检测失败时手动指定是否可用;#include "gtest/gtest.h"后可查GTEST_IS_THREADSAFE宏确认线程安全状态 |
-DGTEST_CREATE_SHARED_LIBRARY=1 | 把 gtest 编译为共享库(Windows 上即 DLL),需配合链接器选项 |
-DGTEST_LINKED_AS_SHARED_LIBRARY=1 | 编译使用共享 gtest 的测试代码时必须加 |
-DGTEST_DONT_DEFINE_FOO=1 | 宏名冲突时改名:FOO可为FAIL、SUCCEED、TEST,加此宏后需写GTEST_TEST(...)而非TEST(...) |
其中"共享库"两条宏,文档建议即便当前编译器(如 GCC)不加也能工作,也应始终加上,以兼容未来的库加载优化(visibility 相关)。
已知限制
Primer 在结尾列出的限制同样值得测试作者注意:googletest 设计为线程安全,但实现仅在 pthread 可用的系统上才是线程安全的;在其他系统(例如 Windows)上,两线程并发调用断言目前并不安全。绝大多数测试都在主线程完成断言,通常不受影响。
延伸阅读
- 入门概念:googletest/docs/primer.md;进阶主题(死亡测试、参数化测试、flag 详解等):googletest/docs/advanced.md;FAQ:googletest/docs/faq.md;
- Mock 扩展:googlemock/README.md 与 googlemock/docs/CookBook.md;
- 仓库内构建指南:docs/BuildRobo3TOnMacAndLinux.md、docs/BuildRobo3TOnWindows.md、docs/BuildRobo3TShell.md;
- 测试工程配置:src/robomongo-unit-tests/CMakeLists.txt 与 src/robomongo-unit-tests/README.md。
如果希望为新模块补测,遵循仓库约定即可:把Foo_test.cpp与Foo.cpp/Foo.h放在一起,按TEST(用例名, 场景_行为)风格书写断言,再把文件登记进 src/robomongo-unit-tests/CMakeLists.txt 的SOURCES_TEST,即可获得与既有测试一致的构建与运行体验。
- 数据库客户端
- 桌面应用
【免费下载链接】robomongo
Native cross-platform MongoDB management tool
相关推荐
wiliwili单元测试框架:Google Test集成
wiliwili单元测试框架:Google Test集成 wiliwili作为专为手柄控制设计的跨平台B站客户端,需要在PC、PSVita、PS4和Ninten
音视频桌面应用构建弹性消息系统:RawRabbit+Polly实现熔断与重试策略
构建弹性消息系统:RawRabbit+Polly实现熔断与重试策略 在构建现代分布式系统时,消息队列已成为系统解耦和异步通信的核心组件。然而,网络不稳定、服务宕
如何告别脚本管理混乱?AUTO-MAS多脚本自动化平台终极指南
如何告别脚本管理混乱?AUTO MAS多脚本自动化平台终极指南 你是否曾经面对电脑屏幕上十几个脚本窗口感到无从下手?是否厌倦了为每个脚本单独配置参数、监控运行状
任务调度工作流自动化GUI 自动化桌面应用后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考