TEN-framework 中的 Google Mock:能力解析、构建方法与集成实践
【免费下载链接】ten-frameworkOpen-source framework for conversational voice AI agents项目地址: https://gitcode.com/TEN-framework/ten-framework
当你需要给一个 C++ 模块写单元测试,而这个模块的行为依赖于外部服务或尚未实现的接口时,直接测"真家伙"既不现实也不稳定。此时你通常会想到引入一个 mock 框架:用假的依赖替代真实依赖,既隔离了外部行为,又能对"被调用了什么、按什么顺序调用"做出精确断言。TEN-framework 仓库里为此准备了一套现成工具——Google Mock,源码完整位于 third_party/googlemock。本文围绕这份捆绑源码展开:先看它的文件布局与能力边界,再逐条拆解它在仓库 GN 工程中的构建目标,然后给出 CMake、Autotools、手工编译等多种接入方式,最后落到核心宏的链式语法与版本演进注意事项。读完你应当清楚:如何在自己的测试里引入它,以及 TEN 的 smoke 测试是如何依赖它的。
一、场景切入与组件定位 🎯
TEN 运行时的测试主要落在 tests/ten_runtime/smoke 下,目录里散布着 300 多个.cc测试文件,覆盖消息传递、命令执行、图引擎、App 生命周期等场景。这些测试要在不启动完整语音链路的前提下断言运行时内部行为,mock 能力就是刚需。仓库没有从包管理器拉取测试库,而是把 googlemock 与 googletest 两个目录并列放进 third_party 统一捆绑:前者负责 mock 类生成与期望校验,后者负责测试用例的注册与执行。值得注意的是,两者在仓库里是强绑定关系——这个约束会直接影响构建配置,第三节展开。
二、内部构造与能力边界 🔍
从目录布局入手理解 googlemock 最直观。头文件全部放在 third_party/googlemock/include/gmock/ 下,职责分层清晰:
- gmock.h:用户唯一需要包含的入口头,它把 actions、matchers、cardinalities、generated 系列等 10 个头文件聚合起来,并声明
InitGoogleMock()初始化函数; - gmock-spec-builders.h:
ON_CALL()与EXPECT_CALL()两大宏的实现,是整个框架的核心; - gmock-actions.h 与 gmock-more-actions.h:定义调用命中后的行为(返回值、调用函数等);
- gmock-matchers.h 与 gmock-cardinalities.h:参数匹配器与调用次数约束(Times/Ge/Le 等);
- 一组
gmock-generated-*.h及其对应的.pump模板(如 gmock-generated-function-mockers.h.pump):mock 类生成器按参数量重复展开的宏代码由构建期模板机制产出,模板生成逻辑在 scripts/generator 目录,scripts/fuse_gmock_files.py 则负责把所有头/源拼成单文件版本。
实现代码集中在 third_party/googlemock/src,共 7 个文件:gmock-all.cc是"全家桶"单文件入口,gmock_main.cc额外提供main(),其余gmock.cc、gmock-spec-builders.cc、gmock-matchers.cc等是模块级实现。
从 gmock.h 的源码可以看到,框架对外暴露了三个全局控制项:catch_leaked_mocks(检测泄漏的 mock 对象)、verbose(输出详细度)、default_mock_behavior(未设定期望时的默认行为)。
归纳其核心能力:
- 用
MOCK_METHOD系列宏声明式生成 mock 类,无需手写样板代码; - 内置丰富的 matchers 与 actions,既能校验参数又能定制返回值;
- 支持无序、部分有序(
.InSequence()/.After())乃至完全有序的调用期望; - 支持 partial mock(真实与 mock 混合对象)、任意类型与重载函数;
- 期望在测试结束时自动验证,不需要 record-and-replay 流程;
- 不依赖异常,可在禁用异常的环境下工作。
这些能力在语言层面靠一条关键取舍实现:C++ 没有运行时反射,所以 mock 类生成、参数类型推导、行为绑定全部通过宏 + 模板在编译期完成。这正是.pump模板存在的根本原因——用脚本批量生成编译期胶水代码,而不是在运行时动态构造。
三、依赖链与项目集成 🧩
先说依赖方向:Google Mock 构建于 Google Test 之上,必须使用捆绑版本的 Google Test,即使你把它接到其他测试框架上,gtest 仍是底层硬依赖。README 对此有明确表述:
You must use the bundled version of Google Test when using Google Mock.
这正是仓库在third_party下并列放置 googlemock 与 googletest 两个目录、且两侧头文件互相引用对方的原因。
落到构建配置上,third_party/googlemock/BUILD.gn 定义了三个目标:
config("gmock_header"):include_dirs除.、include外,还包含../googletest与../googletest/include——头文件搜索路径直接跨目录覆盖了对侧,依赖关系在 config 层面就显性化了;source_set("googlemock"):只编译src/gmock-all.cc,作为库目标供测试链接;source_set("gmoke_main"):编译src/gmock_main.cc,提供带main()的入口版本(目标名拼写就是gmoke_main,引用时别写成gmock_main)。
对侧的 third_party/googletest/BUILD.gn 结构镜像:config("gtest_header")、source_set("googletest")(编译src/gtest-all.cc)、source_set("gtest_main")。它还有多一个ten_package("gtest_system_package")目标,注释里解释得很直白:googletest 全 C++ 写成,若预编译成库分发,编译库的编译器与使用者的编译器不一致时会暴露C++ ABI 兼容性问题,所以系统包选择原样打包源码让使用者本地编译。仓库"两侧都留 source_set + 分发时只给源码"的组织方式,正是为了把 ABI 风险留到消费侧由消费侧的编译器收口。
消费侧看 tests/ten_runtime/smoke/BUILD.gn:聚合目标ten_runtime_smoke_tests与可执行目标ten_runtime_smoke_test的public_deps中都同时列出//third_party/googlemock与//third_party/googletest;子目录测试(如 audio_frame_test/BUILD.gn)同样在public_deps里双引二者,同时引入//third_party/msgpack:msgpackc、//third_party/nlohmann_json做数据序列化断言。整棵 smoke 测试树约 40 个子目录的 BUILD.gn 遵循同一模式,这是"GN 工程里如何接入 Google Mock"的现成范例。
四、构建与接入:四条路径实操
官方推荐的终端用户环境要求如下(贡献者还需 Automake 1.9+、Autoconf 2.59+、Libtool 与 Python 2.3+,后者用于从.pump模板重生成源码):
| 平台 | 要求 |
|---|---|
| Linux | GNU-compatible Make/gmake、POSIX shell、POSIX(-2) 正则、C++98 兼容编译器 |
| Windows | Microsoft Visual C++ 8.0 SP1 或更新 |
| Mac OS X | 10.4 Tiger 或更新,并安装 Developer Tools |
需要说明:这是官方文档编写年代的最低线。从 CMakeLists.txt 声明的版本 1.9.0 与现代 C++ 用法看,实际应以编译器对 C++11 及以上的支持为准。
4.1 CMake 路径
CMakeLists.txt 提供两个开关:BUILD_SHARED_LIBS(默认 OFF)与gmock_build_tests(默认 OFF,开启后可用ctest -R regex挑选 gmock 自测)。gtest 源码定位逻辑值得注意:优先找同目录下的gtest/,找不到就回退到../googletest——与仓库实际布局吻合。
MSVC 分支是一个硬特例:它直接把gtest-all.cc编译进 gmock 库本体,非 MSVC 分支则是gmock链接gtest。脚本注释解释了原因:VC 下两个库共用二进制目录时编译可能出错。VS 2012(MSVC_VERSION EQUAL 1700)还额外add_definitions(/D _VARIADIC_MAX=10)以支撑std::tr1::tuple的可变参数。
融入既有 CMake 工程的最小改动:把链接目标从gtest_main换成gmock_main,并补上头文件路径:
target_link_libraries(example gmock_main) target_include_directories(gmock_main SYSTEM BEFORE INTERFACE "${gtest_SOURCE_DIR}/include" "${gmock_SOURCE_DIR}/include")SYSTEM标记让 clang 在开启-Wpedantic -Wall -Wextra -Wconversion时不告警库头文件里的写法。
4.2 Autotools 路径(Unix)
cd googlemock autoreconf -fvi4.3 手工编译
假设 gtest 在${GTEST_DIR}、gmock 在${GMOCK_DIR},编译两个"全家桶"源文件并打静态库。注意-pthread不可省——两个库内部都用到线程:
g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \ -pthread -c ${GTEST_DIR}/src/gtest-all.cc g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} \ -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} \ -pthread -c ${GMOCK_DIR}/src/gmock-all.cc ar -rv libgmock.a gtest-all.o gmock-all.o然后链接你的测试:
g++ -isystem ${GTEST_DIR}/include -isystem ${GMOCK_DIR}/include \ -pthread path/to/your_test.cc libgmock.a -o your_test仓库 make/Makefile 就是一份可直接运行的自包含示例:GTEST_DIR = ../../googletest、GMOCK_DIR = ..,CPPFLAGS用-isystem处理两侧头目录,CXXFLAGS带-pthread。进入make目录执行make后运行./gmock_test即可验证。它不构建 gmock 自身的测试,适合作为你自研构建脚本的起点。
4.4 MSVC 路径
仓库 msvc 目录提供2005、2010、2015三套工程。切到对应目录执行msbuild gmock.sln可构建库与选中测试;自有项目接入时,通过 Property Manager 添加gmock_config属性表,并把<path to Google Mock>/include加入 Additional Include Directories。
五、核心语法与用法 📐
两个入口头文件头部的注释就是最权威的语法说明书。gmock.h 与 gmock-spec-builders.h 给出的骨架如下:
// 指定 mock 方法的默认动作 ON_CALL(mock_object, Method(argument-matchers)) .With(multi-argument-matcher) // 可选 .WillByDefault(action); // 必须且仅一次 // 声明必须被满足的调用期望 EXPECT_CALL(mock_object, Method(argument-matchers)) .With(multi-argument-matchers) // 可选 .Times(cardinality) // 可选 .InSequence(sequences) // 可多次 .After(expectations) // 可多次 .WillOnce(action) // 可多次 .WillRepeatedly(action) // 可选 .RetiresOnSaturation(); // 可选所有子句均可选,.InSequence()、.After()、.WillOnce()可出现任意次。这套链式写法是"自动验证期望、无需 record-and-replay"的载体:测试结束时框架自行核对所有EXPECT_CALL是否满足。程序入口侧,InitGoogleMock(int* argc, char** argv)负责解析 gmock 认识的命令行参数并顺带初始化 Google Test(对 Windows UNICODE 编译有wchar_t重载)。
六、配置开关与版本演进 ⚙️
gmock 的可调项大多继承 gtest 的控制宏体系,命名形如GTEST_XYZ,定义为 1 或 0 来开关特性,完整清单见 gtest-port.h。
最常碰到的一个开关是TR1 tuple 库选择:gmock 重度依赖 TR1 tuple;编译器不带时会自动回退到 gtest 1.4.0+ 自带的 tuple 子集。若你的项目已在用 TR1 tuple,必须让两侧共用同一套,否则两套实现会冲突:编译 gtest、gmock 与你的测试时统一加-DGTEST_USE_OWN_TR1_TUPLE=0;要强制用自带实现则改为=1。
MSVC 的 tuple 支持对照(源自 CMakeLists.txt 注释):
- VS 2010 及更早:使用 Google Test 自带 tuple;
- VS 2012:
std::tr1::tuple+_VARIADIC_MAX=10; - VS 2013:
std::tr1::tuple; - VS 2015 / VS 2017:直接
std::tuple。
共享库形态下有一条硬约束:gmock 若编成 DLL,同一个 DLL 必须同时包含 Google Test——这正是 CMake 脚本 MSVC 分支把gtest-all.cc直接编入 gmock 库的设计动机。
版本演进方面,仓库捆绑版在 CMake 中声明为 1.9.0。README 记录的破坏性变更点:从 1.1.0 或更早升级时,可能需要显式启用/禁用 gtest 自带 TR1 tuple(即上文宏);从 1.4.0 或更早升级时,pthread 平台的线程安全可能需要调整编译/链接参数;若你曾手写MatcherInterface或MakePolymorphicMatcher()自定义匹配器,需要按新的单态/多态匹配器 API 改写(写法见 docs/CookBook.md),而用MATCHER()/MATCHER_P*()宏定义的匹配器不受影响。
七、文档地图与延伸 📚
third_party/googlemock/docs下保留完整官方文档,建议按以下顺序阅读:
- 先学 googletest/docs/Primer.md 打底——gmock 是构建在 gtest 之上的;
- 读 ForDummies.md 入门,其中含"如何把 gmock 接到任意测试框架"的专门章节;
- CheatSheet.md:常用 API 一页速查;
- CookBook.md:高级实战配方,含新匹配器写法;
- 提问前查 KnownIssues.md 与 FrequentlyAskedQuestions.md;
- 深入原理看 DesignDoc.md,索引入口是 Documentation.md。
动手的最近起点是 tests/ten_runtime/smoke://third_party/googlemock+//third_party/googletest双依赖 +source_set直接编译gmock-all.cc的完整 GN 范式就在其中,挑一个子目录(比如audio_frame_test或msg_test)对照阅读,即可把本文的构建链路在仓库里逐层对上。
【免费下载链接】ten-frameworkOpen-source framework for conversational voice AI agents项目地址: https://gitcode.com/TEN-framework/ten-framework
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考