首页
/ GoogleMock 自定义注入点指南:通过 `gmock/internal/custom/` 定制命令行 Flag 宏体系

GoogleMock 自定义注入点指南:通过 `gmock/internal/custom/` 定制命令行 Flag 宏体系

2026-09-08 13:25:20作者:申梦珏Efrain

导读

本文围绕 GoogleMock(gmock)中一处极易被忽视却承载扩展能力的关键目录 —— googlemock/include/gmock/internal/custom/ 展开。该目录是 GoogleTest 官方刻意预留的「定制注入点(Customization Points)」,允许开发者在不改动框架源码的前提下,为 Google Mock 的命令行 Flag 体系(如 --gmock_verbose--gmock_catch_leaked_mocks 等)注入自定义实现。读完本文,你将理解 custom/gmock-port.h 中 9 个 Flag 相关宏的语义、它们在两条编译路径(Abseil Flags 与内建 Flag)下的展开差异,以及如何利用 GMOCK_DECLARE_* / GMOCK_DEFINE_* / GMOCK_FLAG_GET / GMOCK_FLAG_SET 在自己的项目中新增或替换 Mock Flag,实现框架级行为的深度定制。

一、custom/ 目录是什么:一套「注入即生效」的定制机制

Google Mock 与 Google Test 一样,都把若干内部头文件暴露成可替换的钩子。规则很简单:custom/ 目录下的同名头文件会被框架「内部」头文件以固定路径 #include 引用,因此只要开发者向该目录(或其等价 include 路径)放入自己的版本,就能在不改框架其余源码的情况下覆盖默认行为。

以 Google Mock 为例,googlemock/include/gmock/internal/custom/ 下默认只提供四类文件:

文件 作用对象 状态
gmock-port.h Flag 宏体系(本文核心) 默认空实现
gmock-matchers.h Matcher 扩展注入点 默认空实现
gmock-generated-actions.h Action 生成器扩展注入点 默认空实现
gmock-port.h(custom) 平台相关定制 默认空实现

而框架内部头文件 googlemock/include/gmock/internal/gmock-port.h 在第 57 行以固定语句引入它:

#include "gmock/internal/custom/gmock-port.h"
#include "gtest/internal/gtest-port.h"

其文件头注释把该机制描述得十分直白(见 custom/gmock-port.h):

// Injection point for custom user configurations. See README for details
//
// ** Custom implementation starts here **

gmock/internal/custom/README.md(本文关联文档)对此做了同样精炼的定义:The custom directory is an injection point for custom user configurations——即一个面向自定义用户配置的注入点。它本身不含框架逻辑,而是约定了一套可供覆盖的宏契约;框架所有对 Flag 的读写操作都经由这套宏间接完成,开发者只要保证宏语义一致,框架整体行为即可被替换。

这一设计是 GoogleTest「custom 三件套」体系的一部分,googletest/include/gtest/internal/custom/ 下同样存在 gtest.hgtest-port.hgtest-printers.h 三个注入点(其 gtest.h 支持 GTEST_OS_STACK_TRACE_GETTER_GTEST_CUSTOM_TEMPDIR_FUNCTION_ 等宏,见 gtest 侧的 README)。Google Mock 因为依赖 Google Test,其端口层进一步合并了 gtest 的 gtest-port。

二、gmock-port.h 可定义的全部 Flag 宏

关联文档 googlemock/include/gmock/internal/custom/README.md 明确列出一组可在 custom/gmock-port.h 中定义的 Flag 相关宏,共 9 个,分为三组:

1. 声明宏(DECLARE)

含义
GMOCK_DECLARE_bool_(name) 声明一个 bool 类型的 gmock Flag
GMOCK_DECLARE_int32_(name) 声明一个 32 位有符号整型 gmock Flag
GMOCK_DECLARE_string_(name) 声明一个 std::string 类型的 gmock Flag

声明宏通常在头文件中使用。框架自身就在公共头 googlemock/include/gmock/gmock.h 中一次性声明了三个对外可见的 Flag:

// Declares Google Mock flags that we want a user to use programmatically.
GMOCK_DECLARE_bool_(catch_leaked_mocks);
GMOCK_DECLARE_string_(verbose);
GMOCK_DECLARE_int32_(default_mock_behavior);

这正是 README 中宏的真实消费场景:框架把「想让用户以编程方式控制」的三个 Flag 集中声明,供测试代码读写。

2. 定义宏(DEFINE)

含义
GMOCK_DEFINE_bool_(name, default_val, doc) 定义 bool Flag,含默认值 default_val 与说明文字 doc
GMOCK_DEFINE_int32_(name, default_val, doc) 定义整型 Flag
GMOCK_DEFINE_string_(name, default_val, doc) 定义字符串 Flag

定义宏带三个参数:name(Flag 名,框架内部会统一加上 gmock_ 前缀)、default_val(默认值)、doc(命令行 --help 时展示的说明)。框架的实现位于 googlemock/src/gmock.cc

GMOCK_DEFINE_bool_(catch_leaked_mocks, true,
                   "true if and only if Google Mock should report leaked "
                   "mock objects as failures.");

GMOCK_DEFINE_string_(verbose, testing::internal::kWarningVerbosity,
                     "Controls how verbose Google Mock's output is."
                     "  Valid values:\n"
                     "  info    - prints all messages.\n"
                     "  warning - prints warnings and errors.\n"
                     "  error   - prints errors only.");

GMOCK_DEFINE_int32_(default_mock_behavior, 1,
                    "Controls the default behavior of mocks."
                    "  Valid values:\n"
                    "  0 - by default, mocks act as NiceMocks.\n"
                    "  1 - by default, mocks act as NaggyMocks.\n"
                    "  2 - by default, mocks act as StrictMocks.");

可以看到三个内建 Flag 的默认值与取值域:

  • catch_leaked_mocks:默认 true,泄漏的 mock 对象会被报告为测试失败;
  • verbose:默认值为 testing::internal::kWarningVerbosity(即 "warning"),可取 info / warning / error 三档;
  • default_mock_behavior:默认 1(NaggyMock),0 对应 NiceMock、2 对应 StrictMock。

3. 读写宏(GET/SET)

含义
GMOCK_FLAG_GET(flag_name) 读取某个 gmock Flag 的当前值
GMOCK_FLAG_SET(flag_name, value) 将某个 gmock Flag 设为新值

这两个宏是框架内部使用频率最高的一对,例如在 googlemock/src/gmock-spec-builders.cc 中根据 catch_leaked_mocks 决定是否上报泄漏 mock;在 googlemock/src/gmock-internal-utils.cc 中读取 verbose 决定输出级别;在 gmock-spec-builders.cc 中读取 default_mock_behavior 决定未设置期望的 mock 默认行为。

三、宏的两种展开路径:Abseil Flags vs 内建实现

关联文档只给出了宏名,而宏背后真正做什么googlemock/include/gmock/internal/gmock-port.h 决定——它会在两条路径间按编译条件切换,这决定了开发者覆盖时必须保持的语义契约。

路径 A:启用 Abseil Flags(默认推荐)

当满足 GTEST_HAS_ABSL && !GTEST_NO_ABSL_FLAGS 时(见 gmock-port.h),Flag 底层是 absl::Flag:

#define GMOCK_DEFINE_bool_(name, default_val, doc) \
  ABSL_FLAG(bool, GMOCK_FLAG_NAME_(name), default_val, doc)
#define GMOCK_DECLARE_bool_(name) \
  GTEST_API_ ABSL_DECLARE_FLAG(bool, GMOCK_FLAG_NAME_(name))
#define GMOCK_FLAG_GET(name) ::absl::GetFlag(GMOCK_FLAG(name))
#define GMOCK_FLAG_SET(name, value) \
  (void)(::absl::SetFlag(&GMOCK_FLAG(name), value))

该路径会额外引入 absl/flags/declare.habsl/flags/flag.h(见 gmock-port.h)。

路径 B:内建 Flag(无 Abseil 依赖)

否则退化为框架自带的简单全局变量实现,例如 bool 与 string 的定义与读写分别是:

#define GMOCK_DEFINE_bool_(name, default_val, doc)  \
  namespace testing {                               \
  GTEST_API_ bool GMOCK_FLAG(name) = (default_val); \
  }                                                 \
  static_assert(true, "no-op to require trailing semicolon")
...
#define GMOCK_FLAG_GET(name) ::testing::GMOCK_FLAG(name)
#define GMOCK_FLAG_SET(name, value) (void)(::testing::GMOCK_FLAG(name) = value)

两种路径下共同的命名规约定义在同文件头部(gmock-port.h):

#define GMOCK_FLAG_NAME_(name) gmock_##name
#define GMOCK_FLAG(name) FLAGS_gmock_##name

即:用户侧写 verbose,内部真名是 FLAGS_gmock_verbose,在 testing 命名空间内可见。正因如此,官方文档 docs/gmock_cook_book.md 允许用户在代码里直接通过 ::testing::FLAGS_gmock_verbose = "error"; 这类写法修改 Flag(该方法在当前 gmock 分支的内建路径下依然成立),命令行则使用带前缀的 --gmock_verbose=LEVEL 形式。

结论:若你要在 custom/gmock-port.h 中覆盖宏,必须同时覆盖「声明、定义、读写」三类宏以保持完整语义;可参考 gmock-port.h 里现成的 ABSL / 内建两套实现作为模板。

四、这些宏如何驱动命令行 Flag 的解析

为了让读者理解「覆盖宏」会实际影响什么,这里梳理内建 Flag 的运行时链路——这也是 README 所列宏最重要的消费方。

第一步:InitGoogleMock 统一入口。 用户程序调用 googlemock/src/gmock.cc 提供的 InitGoogleMock(int*, char**)(另有 Windows UNICODE 的 wchar_t** 重载以及 Arduino 等无命令行场景的无参重载),进入 InitGoogleMockImpl

第二步:逐个参数匹配 --gmock_ 前缀。 InitGoogleMockImpl 遍历 argv,把每个参数拼成 --gmock_ + Flag 名的字符串(见 gmock.cc),并通过 GMOCK_FLAG_GET/GMOCK_FLAG_SET 完成读写(见 gmock.cc):

#define GMOCK_INTERNAL_PARSE_FLAG(flag_name)            \
  if (!found_gmock_flag) {                              \
    auto value = GMOCK_FLAG_GET(flag_name);             \
    if (ParseGoogleMockFlag(arg, #flag_name, &value)) { \
      GMOCK_FLAG_SET(flag_name, value);                 \
      found_gmock_flag = true;                          \
    }                                                   \
  }

GMOCK_INTERNAL_PARSE_FLAG(catch_leaked_mocks)
GMOCK_INTERNAL_PARSE_FLAG(verbose)
GMOCK_INTERNAL_PARSE_FLAG(default_mock_behavior)

第三步:命中后从 argv 中剔除该参数并 (*argc)--(见 gmock.cc),保证测试框架与用户程序看到的命令行不再包含已消费的 gmock Flag。

第四步:bool/int/string 各自解析。 ParseGoogleMockFlagValue 负责 --flag=value 的切分;bool 解析把 0/f/F 视为 false、其余为 true;整型走 ParseInt32;字符串直接取值(见 gmock.cc)。另外 InitGoogleMock 内部会先调用幂等的 InitGoogleTest,确保 gtest Flag(如 --gtest_filter)也被一并处理。

因此,运行时控制 gmock 行为与文档一致:命令行传 --gmock_catch_leaked_mocks=0--gmock_verbose=info|warning|error(三种合法级别见 docs/gmock_cheat_sheet.md);--gmock_default_mock_behavior=0/1/2 分别切到 Nice / Naggy / Strict。

五、扩展实践:如何新增一个自定义 gmock Flag

结合上文,若你想复用这套机制新增自己的 Flag,可在自己的代码中按「声明 + 定义」两步完成(框架已把三组宏通过 gmock.h 等头文件暴露给用户):

// my_mock_flags.h —— 声明
#include "gmock/gmock.h"
GMOCK_DECLARE_bool_(log_every_call);   // 自定义:是否记录每次 mock 调用

// my_mock_flags.cc —— 定义(提供默认值与文档串)
GMOCK_DEFINE_bool_(log_every_call, false,
                   "If true, logs every mock method invocation.");

随后在代码中读写:

bool enabled = GMOCK_FLAG_GET(log_every_call);   // 等价于 FLAGS_gmock_log_every_call
GMOCK_FLAG_SET(log_every_call, true);            // 程序内切换

命令行侧会自动获得 --gmock_log_every_call=true 的支持——前提是你把解析逻辑挂在 InitGoogleMock 之后的自定义遍历中(内建 InitGoogleMockImpl 只解析框架自带的三个 Flag)。若你的编译环境启用了 Abseil Flags,则默认支持 --gmock_log_every_call 等 absl 风格的命令行解析;若走内建路径,则需自行扫描 argv

若希望彻底替换框架 Flag 语义(例如将 Flag 存到自定义配置中心),正确做法是在 custom/gmock-port.h 中重新定义 9 个宏,并保证:

  1. GMOCK_FLAG_GET/GMOCK_FLAG_SET 访问的标识符与 GMOCK_DECLARE_*/GMOCK_DEFINE_* 生成的标识符一致;
  2. 定义的类型语义保持 bool / int32_t / std::string
  3. 维护 FLAGS_gmock_ 前缀命名规约,避免破坏 gmock.h 中对三个内建 Flag 的 GMOCK_DECLARE_* 声明与 gmock.ccGMOCK_DEFINE_* 定义。

由于 #include "gmock/internal/custom/gmock-port.h" 是相对 gmock/internal/gmock-port.h 的固定路径(gmock-port.h),把自建头文件放到与仓库相同的 include 目录布局(即 gmock/internal/custom/gmock-port.h),并让该 include 目录优先于仓库头文件目录出现,即可完成注入而无需改动仓库文件。

六、源码级佐证:消费这些宏的关键位置

为便于读者循着宏继续深入仓库,列出当前仓库内消费这些宏的代表性位置(均可直接阅读验证):

结语

gmock/internal/custom/README.md 只用了极短篇幅就描述了一个精巧的扩展机制:通过约定固定 include 路径与 9 个 Flag 宏,Google Mock 把「Flag 是什么、存到哪、怎么读写」全部抽象成可替换的契约。理解这套契约,不仅能解释 --gmock_verbose 等 Flag 为何能在命令行与 FLAGS_gmock_* 全局量之间自由切换,也为深度定制 gmock 行为、移植到特殊平台提供了干净而无侵入的扩展通道。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
925
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
601
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
395