miniblink49 仓库中的 Google Mock 1.7 FAQ 实战解读从 matcher 迁移到模板报错诊断【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49Google Mockgmock是随 V8 5.7 一并内置在 miniblink49 仓库v8_5_7/testing/gmock/目录下的 C 模拟对象框架。本篇以仓库内的官方 FAQv8_5_7/testing/gmock/docs/v1_7/FrequentlyAskedQuestions.md为骨架结合 gmock-matchers.h、gmock-internal-utils.h 与 gmock_doctor.py 等源码完整回答 mock 使用中最常踩的 20 余个问题从方法必须是 virtual的根本前提、1.4.0 之后自定义 matcher 的 API 迁移到 MSVC 编译器陷阱、期望expectation覆盖顺序与失败调试手段。读完你将具备在实际 C 项目中排查 gmock 编译错误、诊断期望不满足、以及正确设计 mock 与 fake 的能力。一、根本前提为什么调用 mock 对象却执行了真实方法FAQ 的第一个问题直指 gmock 最容易踩的坑在 mock 对象上调用方法结果却跑到了真实对象的实现里。答案只有一句话要被 mock 的方法必须是virtual的。Google Mock 的实现原理是生成一个继承自被测接口的子类通过 C 虚函数机制把调用转发到 mock 生成的桩实现上。如果被 mock 的方法不是虚函数编译器在静态类型下直接绑定到基类实现mock 根本无从拦截。class Foo { public: virtual void Bar() 0; // 只有 virtual 方法才能被 gmock 拦截 }; class MockFoo : public Foo { public: MOCK_METHOD0(Bar, void()); };对于无法改造成虚函数的场景FAQ 提到可以用高性能依赖注入技术high-perf dependency injection间接 mock 非虚方法——即把依赖收敛到一个小型接口背后通过该接口调用从而获得可 mock 性。这也与 FAQ 后半部分mock 静态/全局函数的建议一脉相承不要 mock 具体实现而是把耦合点抽象成接口。二、升级 Google Mock 1.4.0 之后自定义 matcher 的 API 迁移重点FAQ 用最大篇幅解释了 1.4.0 之后的一项破坏性变更为了让 matcher 能高效生成更有信息量的失败消息自定义 matcher 的接口从Matches()改为MatchAndExplain()。如果你通过实现MatcherInterface或使用MakePolymorphicMatcher()定义了 matcher旧代码将不再编译而用MATCHER*宏族定义的 matcher 不受影响。这个接口在仓库源码中有明确印证。在 gmock-matchers.h 中MatcherInterface的契约是template typename T class MatcherInterface : public MatcherDescriberInterface { public: virtual bool MatchAndExplain(T x, MatchResultListener* listener) const 0; // 另需实现 DescribeTo(::std::ostream* os) 与可选的 DescribeNegationTo() };配套的MatchResultListenergmock-matchers.h是一个抽象类其operator把解释文本写入底层ostreamIsInterested()让 matcher 在没人想听解释时跳过消息生成以节省开销。2.1 场景一仅实现 MatcherInterface 的最小迁移旧写法1.4.0 之前在Matches()中返回布尔结果using ::testing::MatcherInterface; class MyWonderfulMatcher : public MatcherInterfaceMyType { public: virtual bool Matches(MyType value) const { return value.GetFoo() 5; // 匹配则返回 true } };新写法只需把Matches()重命名为MatchAndExplain()并追加MatchResultListener*参数using ::testing::MatcherInterface; using ::testing::MatchResultListener; class MyWonderfulMatcher : public MatcherInterfaceMyType { public: virtual bool MatchAndExplain(MyType value, MatchResultListener* listener) const { return value.GetFoo() 5; } };2.2 场景二原本使用 ExplainMatchResultTo() 增强消息旧代码通过独立的ExplainMatchResultTo()向::std::ostream*输出解释using ::testing::MatcherInterface; class MyWonderfulMatcher : public MatcherInterfaceMyType { public: virtual bool Matches(MyType value) const { return value.GetFoo() 5; } virtual void ExplainMatchResultTo(MyType value, ::std::ostream* os) const { *os the Foo property is value.GetFoo(); } };迁移时把ExplainMatchResultTo()的逻辑整体搬进MatchAndExplain()原先写入ostream的地方改为写入listenerusing ::testing::MatcherInterface; using ::testing::MatchResultListener; class MyWonderfulMatcher : public MatcherInterfaceMyType { public: virtual bool MatchAndExplain(MyType value, MatchResultListener* listener) const { *listener the Foo property is value.GetFoo(); return value.GetFoo() 5; } };2.3 场景三MakePolymorphicMatcher 的多态 matcher用MakePolymorphicMatcher()定义多态 matcher不绑定具体类型 T时迁移方式完全相同——Matches()改名MatchAndExplain()并加listener参数using ::testing::MakePolymorphicMatcher; using ::testing::MatchResultListener; class MyGreatMatcher { public: bool MatchAndExplain(MyType value, MatchResultListener* listener) const { *listener the Bar property is value.GetBar(); return value.GetBar() 42; } }; // 使用... MakePolymorphicMatcher(MyGreatMatcher()) ...如果旧的多态 matcher 靠外部自由函数ExplainMatchResultTo(const MyGreatMatcher, MyType, ::std::ostream*)输出解释同样要把该逻辑内联进MatchAndExplain()并删除外部函数。仓库内的大量内置 matcher 就是MakePolymorphicMatcher的典型用例例如 gmock-matchers.h 中Eq、HasSubstr、StartsWith、EndsWith等字符串 matcher 都通过MakePolymorphicMatcher(internal::StrEqualityMatcher...)构造。参考这些实现即可掌握正确的迁移范式MatchAndExplain的完整测试覆盖见 gmock-matchers_test.cc 与 gmock-generated-matchers_test.cc。三、测试框架绑定必须用 Google Test 吗FAQ 明确回答不需要。Google Mock 开箱即用支持 Google Test但配置起来很容易让它配合任何测试框架工作——做法是把 gmock 的测试事件TestEventListener或类似的断言接口适配到你自己的框架。仓库中src/gmock_main.cc、src/gmock.cc与test/gmock_all_test.cc展示了与 Google Test 的标准集成方式可作为自定义适配的参考模板。miniblink49 项目本身的单元测试大量使用该框架例如v8_5_7/testing/gmock/test/下全部*_test.cc。四、Google Mock Doctor把模板报错翻译成人话当 gcc 抛出几十行MatcherInterfaceT::...模板错误时先别硬啃。仓库自带一个 Python 工具v8_5_7/testing/gmock/scripts/gmock_doctor.py以#!/usr/bin/env python开头内部维护了一份常见 gmock 符号表见该文件第 43 行起的_COMMON_GMOCK_SYMBOLS。它的工作方式是从 stdin 读取 gcc 错误消息输出对代码问题的诊断FAQ 称之为 diseases。安装建立别名alias gmdpath to googlemock/scripts/gmock_doctor.py在本仓库中即alias gmdv8_5_7/testing/gmock/scripts/gmock_doctor.py使用方式是把构建命令的 stderr 管道给它your-favorite-build-command your-test 21 | gmd典型示例make my_test 21 | gmd也可以直接运行gmd然后把 gcc 的错误消息复制粘贴进去。五、能否 mock 变参函数FAQ 的结论很干脆不能直接 mock 变参函数带...省略号参数的函数。原因在于一般情况下 mock 对象无从得知调用时实际传入了多少个参数、参数类型是什么——只有基类作者清楚协议。要 mock 变参函数用户必须教会mock 如何解析参数个数与类型一个可行方案是提供该函数的重载版本。FAQ 同时给出专业建议省略号参数是从 C 继承的不是 C 特性对带构造/析构函数的参数类型不安全应尽量在 C 中避免。六、MSVC 编译陷阱C4301 / C4373 与 /clr 内存暴涨6.1 const 参数导致的警告在 Visual C 2005 SP1 下编译如下代码class Foo { public: virtual void Bar(const int i) 0; }; class MockFoo : public Foo { public: MOCK_METHOD1(Bar, void(const int i)); };会得到warning C4301: MockFoo::Bar: overriding virtual function only differs from Foo::Bar by const/volatile qualifier在 Visual C 2008 SP1 下则是warning C4373: MockFoo::Bar: virtual function overrides Foo::Bar, previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers本质原因C 规定函数声明顶层的const会被忽略所以class Foo { virtual void Bar(int i) 0; // int 还是 const int 没有区别 };你甚至可以声明为int、定义成const int编译器照样匹配。既然参数级const在声明中毫无意义最佳实践是从Foo和MockFoo中同时移除它即可绕开这个 VC 缺陷。注意这里只讨论顶层 const对指针/引用参数修饰其指向对象pointee/referee的 const 依然有意义以下两个声明并不等价void Bar(int* p); // 指针和所指对象都非 const void Bar(const int* p); // 指针非 const但所指对象是 const6.2 /clr 标志导致的内存耗尽当编译包含巨大 mock 类的翻译单元并启用/clr标志时Visual C 会消耗 5~6 倍内存。建议编译原生 C mock 时避免使用/clr。七、期望不满足时如何调试--gmock_verboseinfo当 gmock 提示期望未满足却不明所以时用--gmock_verboseinfo重新运行测试。该标志会让 gmock打印收到的每一次 mock 函数调用的踪迹通过比对踪迹与期望即可定位偏差。这个标志的取值与语义在源码中有准确定义见 gmock-internal-utils.h取值含义info打印全部日志信息 警告即kInfoVerbositywarning只打印警告即kWarningVerbosityerror不打印任何日志即kErrorVerbosity实际运行时即./your_test --gmock_verboseinfo。八、断言一个函数永不被调用一行搞定EXPECT_CALL(foo, Bar(_)) .Times(0);Times(0)把期望次数限定为零任何一次Bar调用都会立即触发失败。九、为什么失败时同一期望被打印两次每次 gmock 检测到失败都会打印相关信息mock 函数参数、相关期望的状态等辅助调试。当同一期望在两次失败之间状态未变化时你看到的就是两段完全相同的描述。它们并不冗余——对应的是不同的时间点状态保持不变这件事本身就是有价值的线索。十、堆检查失败别忘虚析构函数使用 mock 对象时出现堆检查heap check失败而真实对象正常首先检查你 mock 的基类理想情况下是纯接口是否声明了虚析构函数。FAQ 给出的反面示例class Base { public: ~Base() { ... } // 不是 virtual但应该是 }; class Derived : public Base { private: std::string value_; }; Base* p new Derived; delete p; // 只调用 ~Base()不会调用 ~Derived() —— value_ 泄漏把~Base()改为 virtual 后delete p才会正确触发~Derived()堆检查随之通过。这是所有继承体系通用的纪律mock 场景只是更频繁暴露它。十一、新期望覆盖旧期望规则为什么这么设计很多新手写过这种代码并抱怨必须倒序书写// 期望 foo.Bar() 被调用两次第一次返回 1第二次返回 2。 EXPECT_CALL(foo, Bar()) .WillOnce(Return(2)) .RetiresOnSaturation(); EXPECT_CALL(foo, Bar()) .WillOnce(Return(1)) .RetiresOnSaturation();FAQ 的回应是你只是没选对表达方式。默认情况下期望不要求按任何特定顺序匹配想要顺序就必须显式声明——这是 gmock以及 jMock的核心理念测试很容易被无意中过度指定over-specify框架要让你更难过度指定。有两种更优雅的写法。方式一放入 Sequence 顺序块按自然顺序书写{ InSequence s; EXPECT_CALL(foo, Bar()) .WillOnce(Return(1)) .RetiresOnSaturation(); EXPECT_CALL(foo, Bar()) .WillOnce(Return(2)) .RetiresOnSaturation(); }方式二把动作序列放进同一条期望EXPECT_CALL(foo, Bar()) .WillOnce(Return(1)) .WillOnce(Return(2)) .RetiresOnSaturation();至于为什么 gmock 从后往前搜索期望和ON_CALL这允许用户在早期设置通用行为如 mock 构造函数或 fixture 的SetUp()阶段再用后面的具体规则覆盖定制。若从前向后搜索这一非常有用的模式将不复存在。十二、ON_CALL 设置的行为被调用时为何还报警告即便用ON_CALL(foo, Bar(_)).WillByDefault(...)设置了默认行为没有EXPECT_CALL时调用该方法仍会打印警告。FAQ 明确表态在整洁与安全之间gmock 选择后者。理由是ON_CALL通常在 mock 构造函数或SetUp()中设置作为跨测试基本不变的默认行为而期望EXPECT_CALL才是每个测试各自关心的。设置了ON_CALL并不代表该调用被预期。若无EXPECT_CALL却发生了调用很可能就是 bug——悄悄放行会让缺陷悄悄蔓延。如果确认调用没问题用显式期望替代默认行为即可消除警告EXPECT_CALL(foo, Bar(_)) .WillRepeatedly(...); // 代替 ON_CALL(...).WillByDefault(...)另外可用--gmock_verbose调整输出冗长程度见第七节取值表。十三、在 action 里删除 mock 函数的参数如果 gmock 内置 action 不满足删除参数这类需求FAQ 给出三条出路用MakeAction()定义自己的 action用MakePolymorphicAction()定义多态 action写一个 stub 函数用Invoke()调用它。Invoke()的实现参考 gmock-actions.h 中Return()的写法多态 action 的范式见 gmock-generated-actions.h。十四、MOCK_METHODn 的奇怪语法设计MOCK_METHODn(Method, return_type(args))的第二参数看着别扭但它是刻意设计。假设要 mock 一个参数为 map 的函数virtual int GetSize(const mapint, std::string m);如果采用MOCK_METHOD1(GetSize, int, const mapint, std::string m)这种逐参数语法编译器会把const mapint, std::string m当成两个参数逗号分隔直接编译失败。gmock 的语法把参数类型保护在一对括号里// 这样能正常编译 MOCK_METHOD1(GetSize, int(const mapint, std::string m));FAQ 还补充了该语法的其他优势MOCK_METHOD1(Foo, int, bool)会让人困惑方法到底返回int还是boolgmock 的写法不存在歧义这种函数类型描述语法源自 Ctr1的function库大量使用而 tr1 将成为新标准 STL 的一部分与之保持一致很自然gmock 其他 API如 action 接口也使用同一函数类型语法用户使用高级特性时反正要学不如统一。唯一例外如果返回类型里包含裸逗号仍然需要typedef起别名——但这种情况罕见得多。十五、mock 静态/全局函数FAQ 回答可以但需要改造。一旦出现想 mock 静态函数的需求通常说明模块耦合过紧灵活性、可复用性、可测试性都受损。更优解是定义一个小的接口把调用收敛到该接口上再 mock 它——前期少量投入长期回报显著。这与第一节非虚方法的结论殊途同归mock 能力倒逼出更好的依赖抽象。十六、mock 做复杂操作很痛苦你可能用错了工具FAQ 大方承认gmock 擅长交互式测试interaction-based testing——验证 mock 是否被以正确的方式调用一旦出错立即报告让你精确定位错误触发时的上下文而传统无 mock 测试先执行代码、再断言返回值或系统状态属于基于状态的测试state-based testing。如果你做的是状态测试只是想让一个 test double 模拟真实对象那应该用 fake测试替身而非 mock。用 mock 执行复杂动作是它的弱项因此感到痛苦往往意味着没选对工具或解错了问题。十七、收到 Uninteresting function call encountered 警告该慌吗FAQ 的答复非常直接完全不必恐慌这只是一条通知FYI。含义是某个 mock 函数没有任何期望按 gmock 规则即你对它的调用不感兴趣允许任意次数调用而它确实被调用了——这本身完全合法。gmock 打印该消息是为了覆盖另一种情况你其实想禁止该调用却忘了写EXPECT_CALL(foo, Bar()).Times(0)。当你看到此消息且认为不应有无趣调用时就该调查了gmock 会顺带打印函数名和参数方便定位。十八、SetArgPointee 报 conflicting return type specifiedSetArgPointee()只声明了副作用把第 n 个指针参数指向的值改为某值没有声明返回值——当 mock 方法有返回值时编译器就报冲突的返回类型。解决办法是用DoAll()把副作用和返回值串起来EXPECT_CALL(foo, Bar(_)) .WillOnce(DoAll(SetArgPointee0(42), // 先设置指针所指的值 Return(true))); // 再指定返回值十九、自定义 actionInvoke() 还是实现 action 接口两种方式都可行按场景选更顺手的一个action 只服务于某一种特定函数类型用Invoke()更简单action 可复用于多种函数类型比如Return(value)本身用MakePolymorphicAction()最省事需要精确控制 action 可用的函数类型实现ActionInterface。仓库中Return()的实现见 gmock-actions.h就是绝佳范本。二十、问题不在 FAQ 里怎么办FAQ 提供了三条求助路径查阅仓库内其余文档如 v1_7/CookBook.md、v1_7/ForDummies.md、v1_7/CheatSheet.md、检索邮件列表归档、或向官方讨论组提问。注意不要用 issue tracker提问——它维护频率极低。提问时尽量提供使用的 gmock 版本或 SVN 修订号、操作系统、编译器名称与版本、完整编译命令行、完整编译错误消息、以及能复现问题的最小完整程序。这些信息在本仓库场景下对应v8_5_7/testing/gmock目录中的 gmock 1.7 源码、你的平台与编译器版本以及make my_test 21 | gmd的输出。小结这份 FAQ 的价值在于它浓缩了 gmock 的设计哲学与工程纪律virtual 是 mock 的前提、接口抽象是 mock 的土壤、MatchAndExplain是消息质量的保证、期望从后向前搜索成就了先通用后定制的测试模式。结合 miniblink49 仓库内v8_5_7/testing/gmock/的完整源码与测试你可以把每个问题从该怎么做下沉到为什么这么做在编写和调试自己的 mock 测试时少走弯路。【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考