- 数据库客户端
- 桌面应用
【免费下载链接】robomongo
Native cross-platform MongoDB management tool
本篇文章以 Robomongo(Robo 3T 的前身,原生跨平台 MongoDB 管理工具)源码仓库中的 src/robomongo-unit-tests/README.md 为骨架,结合 src/robomongo-unit-tests/CMakeLists.txt 及三组真实测试源码,系统讲解该项目的单元测试组织约定、GoogleTest 接入方式、robo_unit_tests目标的构建与链接细节,以及现有测试用例的写法与可验证的底层实现。读完本文,你将能快速定位 Robomongo 的测试文件、理解"被测源码与测试文件同目录平级"的独特约定,并掌握在其上新增测试对(Foo.cpp / Foo_test.cpp)的正确姿势。
一、测试模块的定位与目录约定
1.1 一个"为 CMake 占位"的目录
src/robomongo-unit-tests/README.md第一句便开宗明义:"This is just a placeholder forcmake."这句话包含两层信息:
- 该目录不是一个独立的测试源码树,真正的测试代码并不存放在这里;
- 它存在的意义是承载 CMake 构建目标,让
robo_unit_tests可执行文件有一个明确的"家"。
从根目录 CMakeLists.txt 可以看到add_subdirectory(src/robomongo-unit-tests),也就是说整个测试目标是由该子目录的 CMakeLists.txt 定义的。与此同时,测试用例源码与待测源码同目录存放:
robomongo/src/robomongo/ ├── utils/ │ ├── RoboCrypt.cpp │ ├── RoboCrypt_test.cpp │ ├── StringOperations.cpp │ └── StringOperations_test.cpp └── core/ └── HexUtils.cpp └── HexUtils_test.cpp这正是 README 强调的惯例:测试源文件与被测文件位于同一目录、同一层级。这一"test-sibling"(测试与源码平级)布局,与许多项目单独划分tests/目录的做法不同,其好处是显而易见的:
- 可发现性:写代码和写测试的人处在同一目录,随时能看到对应的
_test.cpp,降低"只写实现、不写测试"的概率; - 就近引用:测试可以直接以相对包含(如
#include "HexUtils.h")方式引入被测头文件,不必绕行长长的include路径; - 构建聚合:CMake 里可以用
list(FILTER ... INCLUDE REGEX "cpp")等方式统一聚合源文件,测试文件天然与源码文件混编在同一构建单元中。
1.2 目录中的实际文件
当前仓库src/robomongo-unit-tests/下只有两个文件:
| 文件 | 作用 |
|---|---|
README.md | 说明目录定位与测试文件放置约定 |
CMakeLists.txt | 定义robo_unit_tests可执行目标、gtest 链接与平台差异化配置 |
二、GoogleTest 接入与robo_unit_tests目标的构建机制
2.1 测试框架选型:GoogleTest
src/robomongo-unit-tests/CMakeLists.txt顶部注释即写明"Unit Testing with Google Test"。仓库在 src/third-party/googletest-1.8.1/ 中随源码一同携带了 googletest 1.8.1(含 googlemock),并在 src/third-party/CMakeLists.txt 中作为第三方依赖引入,因此无需系统级安装 gtest 即可构建测试。
构建脚本中的关键配置:
enable_testing() include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)enable_testing():启用 CTest,使ctest能够发现并运行robo_unit_tests中的用例;include_directories:把 googletest 的头文件目录加入编译搜索路径,测试源码中可直接#include "gtest/gtest.h";gtest_force_shared_crt:强制 gtest 使用共享 CRT(Windows 场景下避免运行库冲突)。
2.2 测试源文件的登记方式
CMakeLists 中对"如何新增测试对"给出了明确约定:
New test & source file pairs MUST have the following format:
- Test file:
/path/Foo_test.cpp- Source file:
/path/Foo.cppor/path/Foo.h
即:凡新增测试,必须遵循Foo_test.cpp对应Foo.cpp(或Foo.h)的命名对称,然后把测试文件路径追加到SOURCES_TEST变量中。当前登记的三个测试文件为:
set(ROBO_SRC_DIR ${CMAKE_HOME_DIRECTORY}/src/robomongo) set(SOURCES_TEST ${ROBO_SRC_DIR}/utils/RoboCrypt_test.cpp ${ROBO_SRC_DIR}/utils/StringOperations_test.cpp ${ROBO_SRC_DIR}/core/HexUtils_test.cpp )ROBO_SRC_DIR指向src/robomongo,这与 README 中"测试文件位于 robomongo/src/robomongo/ 及其子目录"的说明完全吻合。
2.3 可执行目标与链接关系
add_executable(robo_unit_tests ${SOURCES_TEST}) add_dependencies(robo_unit_tests robomongo) target_include_directories(robo_unit_tests PRIVATE ${CMAKE_HOME_DIRECTORY}/src)add_executable(robo_unit_tests ...):生成名为robo_unit_tests的测试可执行文件;add_dependencies(robo_unit_tests robomongo):确保主程序robomongo目标先构建,测试二进制随后链接其目标文件;target_include_directories(..., src):把仓库根src加入测试目标私有头文件路径,使测试代码能以robomongo/utils/RoboCrypt.h这种"以 src 为根"的路径引入头文件。
链接阶段,脚本先从robomongo目标取出全部源码,剔除main.cpp、只保留.cpp:
get_target_property(ROBO_SOURCES robomongo SOURCES) list(FILTER ROBO_SOURCES INCLUDE REGEX "cpp") list(FILTER ROBO_SOURCES EXCLUDE REGEX "main.cpp")随后按平台把每个源码文件翻译成目标文件路径(Windows 为*.obj,macOS 为*.cpp.o),连同 Qt 自动生成的mocs_compilation、qrc_gui、qrc_robo等对象一并收集到ROBO_OBJ_FILES中,最终与 gtest 及各类库一起链接:
target_link_libraries(robo_unit_tests gtest gtest_main Qt5::Widgets Qt5::Network Qt5::Xml ${WebEngineWidgets} qjson qscintilla mongodb ssh Threads::Threads ${ROBO_OBJ_FILES} )gtest与gtest_main提供了框架运行时与main入口,使测试文件只需写TEST(...)宏、无需自带 main;qjson、qscintilla、mongodb、ssh等则是 Robomongo 主程序在解析 JSON、脚本编辑、MongoDB 客户端与 SSH 隧道等模块用到的内部/第三方库,测试目标把它们全部链接进来,意味着测试用例可以触及核心业务代码。
2.4 平台差异与已知限制
Linux 当前禁用:脚本开头有一段醒目提示:
if (SYSTEM_LINUX) message("\n *Note: Currently unit testing is disabled for Linux " "due to MongoDB linking problems") return() endif()也就是说在 Linux 平台上,单元测试目标目前被主动跳过(README 中 "placeholder for cmake" 的说法也与这种"仅保留构建占位"的现状呼应),Linux 分支的目标文件收集代码也整段处于注释状态。这一限制源于 MongoDB 客户端库在 Linux 下的链接问题,属于当前仓库的实际状态说明。
macOS 特殊处理:额外链接
Security、CoreFoundation与-lresolv,以补全 macOS 下解析器与安全框架依赖。Windows DLL 部署:Debug 构建时向文件名追加
d后缀(Qt5Cored.dll等),并把 Qt5 系列 DLL(Core/Gui/Network/PrintSupport/Widgets/Xml/Positioning/Qml/Quick/QuickWidgets/WebChannel/WebEngine*)以及 OpenSSL 的libssl-1_1-x64.dll、libcrypto-1_1-x64.dll复制到测试二进制输出目录,保证测试运行时 DLL 可达。
三、现有测试用例逐条解析
3.1 RoboCrypt:加解密往返一致性
RoboCrypt_test.cpp 是当前唯一一个"实质性"测试用例,它验证Robomongo::RoboCrypt的加密与解密是互逆的:
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); } }用例选取了四类输入:普通字母数字串、纯符号串、混合转义符号串与引号串,覆盖了密码/凭据中常见字符集。其底层实现位于 RoboCrypt.cpp 与 RoboCrypt.h:encrypt/decrypt本质是对SimpleCrypt的薄封装,将std::string转成QString后调用encryptToString/decryptToString。而SimpleCrypt(SimpleCrypt.h)是一个使用64 位密钥的简单加解密类,其头文件注释明确警告:
The encryption provided by this class is NOT strong encryption. It may help to shield things from curious eyes, but it will NOT stand up to someone determined to break the encryption.
因此 RoboCrypt 用于防止"路过的好奇目光"(例如明文密码直接落盘),而非对抗专业攻击者。RoboCrypt::initKey()的密钥管理策略也值得注意:优先从~/.3T/robo-3t/robo3t.key读取已有密钥;若不存在则用std::mt19937_64在2^61 ~ 2^62区间生成新密钥并写回该文件,全程通过_roboCryptLogs记录日志与严重级别(RoboCrypt.cpp)。测试所依赖的SimpleCrypt静态实例正是以这把_KEY初始化的。
3.2 StringOperations:首字符大写化
StringOperations_test.cpp 只有一个用例,验证captilizeFirstChar:
TEST(StringOperationsTests, captilizeFirstChar) { // EXPECT_EQ("Abcc", Robomongo::captilizeFirstChar("abc")); // Simulating failing test EXPECT_EQ("Abc", Robomongo::captilizeFirstChar("abc")); // Simulating passing test }注释里同时保留了"模拟失败用例"与"模拟通过用例"两行,可看作编写断言的示例。被测实现位于 StringOperations.cpp:
std::string captilizeFirstChar(std::string str) { if (!str.empty()) str[0] = static_cast<char>(toupper(str[0])); return str; }即对非空字符串的首字符执行toupper。头文件 StringOperations.h 解释了该函数的动机:"Mongo errors often come all lower case"——MongoDB 返回的错误信息常为全小写,Robomongo 用它把错误提示的首字母大写,改善 UI 展示。可见该工具函数服务于 Mongo 错误信息的格式化,是 GUI 与底层 MongoDB 客户端之间的一层字符串规整逻辑。
3.3 HexUtils:十六进制判定
HexUtils_test.cpp 验证Robomongo::HexUtils::isHexString:
TEST(hex_utils_tests, test_1) { EXPECT_TRUE(Robomongo::HexUtils::isHexString("a")); }被测实现位于 HexUtils.cpp:逐字符调用isxdigit检查,任一字符非十六进制字符即返回false。同文件还提供toStdHexLower(底层复用mongo::toHexLower)与fromHex(要求输入长度为偶数,否则返回 NULL),说明 HexUtils 是 Robomongo 与 MongoDB 驱动之间做二进制/十六进制互转的桥接工具,其正确性直接影响 BSON 数据展示等核心功能。
3.4 测试文件的通用写法范式
三个测试文件头部都带有一段相同的注释模板,实际起到了"团队测试规范"的作用:
TEST( [Test_Case_Name], [Test_Name] ) TEST( [Test_Case_Name], [UnitOfWorkName_ScenarioUnderTest_ExpectedBehavior] ) TEST( StringParserTests, NumberLeftOf_StringWithoutNumber_ReturnsFalse) { // ... }它提倡两级命名:第一参数为测试套件名(如RoboCrypt_CoreTests、StringOperationsTests、hex_utils_tests),第二参数为具体行为描述,推荐采用被测单元_场景_期望行为的句式,让失败时gtest输出的用例名自带可读语义。这也解释了为什么断言宏优先使用EXPECT_EQ/EXPECT_TRUE(非致命断言,失败后继续执行本用例),而不是ASSERT_*。
四、与主程序测试入口的关联
除了 gtest 体系,仓库还保留了另一条"轻量自检"路线:主程序目录下的 main_test.cpp 不依赖 gtest,而是直接用assert检查 MongoDB 驱动的两个行为点:
HostAndPort的toString():验证 IPv4、IPv6(含方括号包裹的边界行为)的格式化输出,例如断言HostAndPort("2a03:b0c0:3:d0::f3:1001", 20017)会序列化为[2a03:b0c0:3:d0::f3:1001]:20017,并顺带记录了 MongoDB 3.2 中"已含方括号仍会再次包裹"的已知行为;double的精度输出:用std::numeric_limits<double>::digits10控制流精度,逐一断言-9.987654321、1.1、3.1415等值的字符串序列化结果。
这与robo_unit_tests形成互补:gtest 目标覆盖项目自身工具函数,main_test.cpp则直接校验依赖的 MongoDB 客户端库在 Robomongo 使用方式下的行为是否符合预期。
五、如何新增一个测试:实操指南
结合 README 约定与 CMake 脚本,在 Robomongo 中新增一个单元测试需要三步:
写被测代码:在
src/robomongo/相应子目录(如utils/、core/)下放置Foo.cpp(或仅Foo.h);同目录新建
Foo_test.cpp:与Foo.cpp平级放置,命名必须为Foo_test.cpp,内容形如:#include "gtest/gtest.h" #include "Foo.h" TEST(FooTests, Bar_Scenario_Expected) { EXPECT_EQ(/* 期望值 */, Robomongo::bar(/* 输入 */)); }头文件按 CMake 中
target_include_directories(robo_unit_tests PRIVATE src)的配置,既可写#include "robomongo/utils/Foo.h",也可按实际相对关系写#include "Foo.h";登记到
SOURCES_TEST:在 src/robomongo-unit-tests/CMakeLists.txt 的SOURCES_TEST列表中追加${ROBO_SRC_DIR}/<子目录>/Foo_test.cpp,重新配置并构建后,robo_unit_tests可执行文件即包含新用例。
之后即可通过ctest或直接运行robo_unit_tests可执行文件执行测试。需要留意的是:当前仓库在 Linux 平台下该目标会被 CMake 脚本提前return()跳过,实测请优先在 Windows / macOS 环境下构建;Linux 下如需验证相关工具函数,可借助main_test.cpp这类不依赖 gtest 的自检入口,或参照 docs/BuildRobo3TOnMacAndLinux.md、docs/BuildRobo3TOnWindows.md 了解完整的构建流程与平台前提。
六、小结
Robomongo 的单元测试体系可以用三句话概括:
- 布局约定:测试文件与被测文件"同目录平级",命名严格遵循
Foo_test.cpp↔Foo.cpp/Foo.h的对仗关系,目录本身仅为 CMake 占位(见 src/robomongo-unit-tests/README.md); - 构建机制:由 src/robomongo-unit-tests/CMakeLists.txt 基于 googletest 1.8.1 生成
robo_unit_tests目标,复用主程序robomongo的全部对象文件,并针对 Windows/macOS 做了 DLL 部署与系统库链接的差异化处理,Linux 平台因 MongoDB 链接问题暂时禁用; - 现有覆盖:三个 gtest 用例分别覆盖密码加解密往返(RoboCrypt + SimpleCrypt)、Mongo 错误信息首字母大写化(StringOperations)与十六进制字符串判定(HexUtils),另有
main_test.cpp承担 MongoDB 驱动HostAndPort/double精度输出的断言式自检。
这套"以源码树为骨架、就近测试"的组织方式,与 src/third-party/googletest-1.8.1/ 的随仓依赖策略相结合,构成了一个对贡献者足够低门槛、对构建系统足够显式的测试基础设施,是研究 Robomongo 内部质量保障机制的最佳切入点。
- 数据库客户端
- 桌面应用
【免费下载链接】robomongo
Native cross-platform MongoDB management tool
相关推荐
WeexCore 测试工程集成 Google Test 实战:googletest 构建方式与 CMake 配置全解
WeexCore 测试工程集成 Google Test 实战:googletest 构建方式与 CMake 配置全解 导读 Google Test(google
移动开发跨平台前端从 `cargo test` 看 Cargo 的测试体系:单元测试、集成测试与文档测试全解析
从 cargo test 看 Cargo 的测试体系:单元测试、集成测试与文档测试全解析 cargo test 是 Cargo 提供的统一测试入口:一条命令即可
开发工具包管理器CLI构建工具Apache WeexCore 测试体系实战:基于 Google Test(googletest)的 C++ 单元测试框架集成指南
Apache WeexCore 测试体系实战:基于 Google Test(googletest)的 C++ 单元测试框架集成指南 Google Test 是
移动开发跨平台原生移动前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考