Google Mock v1.7 常见问题深度指南:模板错误诊断、Matcher API 迁移与 Mock 测试实战(miniblink49 内置 gmock 解析)
【免费下载链接】miniblink49a lighter, faster browser kernel of blink to integrate HTML UI in your app. 一个小巧、轻量的浏览器内核,用来取代wke和libcef项目地址: https://gitcode.com/GitHub_Trending/mi/miniblink49
本文以 miniblink49 仓库内置的 Google Mock(gmock)v1.7 官方 FAQ 文档(v8_6_7/testing/gmock/docs/v1_7/FrequentlyAskedQuestions.md)为主体,系统梳理使用 gmock 时最容易踩坑的二十余个高频问题:从"mock 方法为何调到了真实对象"、Matcher 新老 API 迁移,到 GCC/ MSVC 编译器报错解读、堆检查失败与期望覆盖规则。文中每个结论均结合仓库内 gmock 源码 与 测试用例 给出依据,读完你不仅能避开这些坑,还能理解 gmock 底层设计哲学,让 mock 测试从"能用"走向"用好"。
在 miniblink49 中,Google Mock 以 v8 引擎测试基础设施的形式随 v8_6_7/testing/gmock/ 目录一并提供,包含完整的头文件(include/gmock/)、实现(src/)、构建脚本(make/、msvc/、scripts/)以及约 20 个测试文件(test/)。这份 FAQ 正是 gmock 作者对用户反馈的沉淀,本文按原文档脉络逐条展开,并补充源码级证据。
一、Mock 方法为何调用了真实对象?——必须 virtual
症状:对 mock 对象调用某个方法时,执行的是真实对象的方法。
原因:gmock 通过继承并在子类中重写基类方法来实现 mock,而 C++ 中只有virtual成员函数才能被正确重写(overriding)。如果一个方法没有virtual修饰,基类指针/引用调用它时走的仍是基类实现,mock 版本永远不会被触发。
解决:
- 将被 mock 的方法声明为
virtual; - 如果基类方法不是 virtual 且无法修改,可以采用文档提到的high-perf dependency injection technique(高性能依赖注入技巧)——为被测代码引入一个轻量接口,把依赖的函数调用收敛到接口上,再 mock 这个接口。该技巧的详细配方见仓库内 CookBook 的 "Mocking Nonvirtual Methods" 章节。
延伸:这也是 gmock 设计上的第一原则——"能被 mock 的前提是接口可替换"。从仓库源码看,所有 mock 类都通过MOCK_METHODn宏展开为对基类虚函数的 override(见 gmock-generated-function-mockers.h),因此虚函数表(vtable)机制是 gmock 运转的根基。
二、升级 gmock 后自定义 Matcher 不再编译:Matches() 到 MatchAndExplain() 的迁移
背景:gmock 1.4.0 之后,为了让 matcher 能高效生成更丰富的信息化错误消息,官方对 matcher 的扩展 API 做了不兼容调整。如果你是通过实现MatcherInterface或使用MakePolymorphicMatcher()编写自定义 matcher,旧代码将无法编译;而用MATCHER*宏族定义的 matcher 不受影响。
迁移的核心只有一句话:把Matches()改名为MatchAndExplain(),并新增一个MatchResultListener*参数。
2.1 实现 MatcherInterface 的迁移
旧写法(升级后无法编译):
using ::testing::MatcherInterface; ... class MyWonderfulMatcher : public MatcherInterface<MyType> { public: ... virtual bool Matches(MyType value) const { // Returns true if value matches. return value.GetFoo() > 5; } ... };新写法(与最新 gmock 兼容):
using ::testing::MatcherInterface; using ::testing::MatchResultListener; ... class MyWonderfulMatcher : public MatcherInterface<MyType> { public: ... virtual bool MatchAndExplain(MyType value, MatchResultListener* listener) const { // Returns true if value matches. return value.GetFoo() > 5; } ... };即:重命名Matches()为MatchAndExplain(),并添加类型为MatchResultListener*的第二个参数。
2.2 原先用 ExplainMatchResultTo() 增强消息时的迁移
如果旧 matcher 还重写了ExplainMatchResultTo()来输出辅助信息:
using ::testing::MatcherInterface; ... class MyWonderfulMatcher : public MatcherInterface<MyType> { public: ... virtual bool Matches(MyType value) const { return value.GetFoo() > 5; } virtual void ExplainMatchResultTo(MyType value, ::std::ostream* os) const { // Prints some helpful information to os to help // a user understand why value matches (or doesn't match). *os << "the Foo property is " << value.GetFoo(); } ... };迁移方式:把ExplainMatchResultTo()的逻辑搬进MatchAndExplain(),原来写向::std::ostream*的内容改写到MatchResultListener*:
using ::testing::MatcherInterface; using ::testing::MatchResultListener; ... class MyWonderfulMatcher : public MatcherInterface<MyType> { public: ... virtual bool MatchAndExplain(MyType value, MatchResultListener* listener) const { // Returns true if value matches. *listener << "the Foo property is " << value.GetFoo(); return value.GetFoo() > 5; } ... };2.3 基于 MakePolymorphicMatcher() 的迁移
用MakePolymorphicMatcher()定义多态 matcher 的旧代码:
using ::testing::MakePolymorphicMatcher; ... class MyGreatMatcher { public: ... bool Matches(MyType value) const { return value.GetBar() < 42; } ... }; ... MakePolymorphicMatcher(MyGreatMatcher()) ...同样只需改名并加参数:
using ::testing::MakePolymorphicMatcher; using ::testing::MatchResultListener; ... class MyGreatMatcher { public: ... bool MatchAndExplain(MyType value, MatchResultListener* listener) const { return value.GetBar() < 42; } ... }; ... MakePolymorphicMatcher(MyGreatMatcher()) ...2.4 多态 matcher 使用 ExplainMatchResultTo() 时的迁移
旧代码把解释逻辑放在独立的ExplainMatchResultTo(const MyGreatMatcher&, MyType, ::std::ostream*)自由函数中:
using ::testing::MakePolymorphicMatcher; ... class MyGreatMatcher { public: ... bool Matches(MyType value) const { return value.GetBar() < 42; } ... }; void ExplainMatchResultTo(const MyGreatMatcher& matcher, MyType value, ::std::ostream* os) { *os << "the Bar property is " << value.GetBar(); } ... MakePolymorphicMatcher(MyGreatMatcher()) ...迁移后,把解释逻辑移入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()) ...2.5 源码层面理解 MatchResultListener 的设计动机
仓库源码 gmock-matchers.h 明确定义了MatchResultListener:它是一个抽象类,重载了operator<<,当底层 ostream 为 NULL 时写入操作是空操作。它提供两个关键方法:
IsInterested():返回底层流是否为非空,matcher 可以据此避免在无人需要解释时生成昂贵的解释文本——这正是 1.4.0 之后引入该 API 想解决的性能问题;stream():返回底层::std::ostream*。
新的MatcherInterface<T>唯一需要实现的匹配方法是纯虚的MatchAndExplain(T x, MatchResultListener* listener)(见 gmock-matchers.h)。此外,源码还提供了三种具体 listener:
StringMatchResultListener:把解释累积到std::stringstream,供内部拼装失败消息用(gmock-matchers.h);DummyMatchResultListener:丢弃所有解释,用于"只关心是否匹配"的Matches()快速路径(gmock-matchers.h);StreamMatchResultListener:把解释转发给指定 ostream(gmock-matchers.h)。
而MakePolymorphicMatcher()返回的PolymorphicMatcher<Impl>会在内部把 Impl 适配成每个具体类型的MonomorphicImpl,并把MatchAndExplain委托给 Impl(gmock-matchers.h)。因此,无论你采用哪种方式写 matcher,最终都会落在MatchAndExplain契约上。官方另外的 monomorphic/polymorphic matcher 编写配方可继续参考 CookBook 的 "Writing New Monomorphic Matchers" 与 "Writing New Polymorphic Matchers" 两节。
三、用 gmock 就必须用 Google Test 吗?——完全不必
gmock 开箱即用地与 Google Test 协同工作,但它很容易配置成与任意测试框架配合使用。核心做法是提供一个适配层:gmock 对外部断言/失败机制的唯一依赖是一个用于报告失败的"空操作"函数,你只需把该函数重定向到自己的测试框架的失败处理即可。具体配置步骤见 ForDummies 的 "Using Google Mock with Any Testing Framework" 一节。
这一设计也印证了 gmock 的分层结构:gmock.h只依赖一组可插拔的宏与函数,src/中的实现(如 gmock-spec-builders.cc)并不强绑定 gtest 内部类型。
四、Google Mock Doctor:把可怕的模板错误翻译成人话
症状:gcc 抛出一屏难以理解的模板错误,不知所措。
方案:使用 gmock 自带的Google Mock Doctor工具。它读取 stdin 中的 gcc 错误输出,把 gmock 特有的编译错误(官方称之为 "diseases",疾病)翻译成可读的诊断说明。该工具在仓库中的实现位于 scripts/gmock_doctor.py,其源码确认:工具从 stdin 读取文本(sys.stdin.read(),见 gmock_doctor.py),并内置了一张 gmock 常见符号表(_COMMON_GMOCK_SYMBOLS)用于识别错误上下文。
安装(配置别名):
alias gmd='<path to googlemock>/scripts/gmock_doctor.py'在 miniblink49 仓库中,对应路径为:
alias gmd='v8_6_7/testing/gmock/scripts/gmock_doctor.py'使用方式一:管道喂入编译输出:
<your-favorite-build-command> <your-test> 2>&1 | gmd例如:
make my_test 2>&1 | gmd使用方式二:交互式粘贴:直接运行gmd,然后把 gcc 的错误消息复制粘贴给它即可。
五、能 mock 可变参数函数(variadic function)吗?
不能直接 mock。gmock 的 mock 机制要求编译期确定每个参数的数量与类型,而带省略号(...)的可变参数函数恰恰无法在编译期获知这些信息——只有基类作者才知道调用协议,mock 框架"无法看穿他的大脑"。
可行的替代方案:由用户自己提供该函数的重载版本(overloads),把可变参数协议显式化为若干固定签名,再对这些重载进行 mock。
官方态度:省略号参数继承自 C,并非真正的 C++ 特性;它不安全,且无法与带有构造/析构函数的参数类型配合使用。因此建议在 C++ 中尽可能避免使用可变参数。
六、MSVC 报 C4301 / C4373 警告:const 参数引起的"假"差异
在 Visual C++ 2005 SP1 下编译如下代码:
class Foo { ... virtual void Bar(const int i) = 0; }; class MockFoo : public Foo { ... 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根因分析:这是 MSVC 的 bug(同样的代码在 gcc 下编译无碍)。C++ 语言规则规定:函数声明中的顶层const参数修饰会被忽略。也就是说:
class Foo { ... virtual void Bar(int i) = 0; // int or const int? Makes no difference. };上面两个Bar声明完全等价——你甚至可以用int声明、用const int定义,编译器仍认为它们是同一个函数。
解决:既然方法声明中参数加const没有意义,官方建议在Foo和MockFoo中同时移除这个顶层 const,即可绕过 VC 的 bug。
重要区分:以上讨论的只是顶层(top-level)const。如果参数是指针或引用,那么被指对象(pointee)或被引用对象(referee)的 const 依然有意义,以下两个声明并不等价:
void Bar(int* p); // Neither p nor *p is const. void Bar(const int* p); // p is not const, but *p is.七、巨型 mock 类导致 MSVC 编译内存耗尽?
gmock 发现:当启用/clr(公共语言运行时支持)编译开关时,VC 编译一个 mock 类会消耗约 5~6 倍的内存。建议:编译原生 C++ mock 时避免使用/clr。如果你必须同时使用托管代码,应把 mock 相关源文件拆出单独以纯原生方式编译。
八、期望总是不满足?用 --gmock_verbose=info 看调用轨迹
症状:测试失败,但搞不清 gmock 为什么认为期望未满足。
方案:带上--gmock_verbose=info运行测试。该 flag 会让 gmock打印它收到的每一次 mock 函数调用的轨迹(trace),通过研读轨迹即可定位期望未满足的原因。
补充说明:gmock 的 verbose 级别共有三个——info、warning、error(见 CheatSheet 的命令行 flag 表)。日常调试用info获得最全信息;嫌输出太吵时可以退回warning甚至error。verbose 级别同样可以在代码里通过::testing::FLAGS_gmock_verbose全局变量控制(参考 CookBook)。
九、如何断言某个函数"从未被调用"?
使用.Times(0):
EXPECT_CALL(foo, Bar(_)) .Times(0);这是最常见的"负面期望"写法,与 gmock 的规则一致:未显式设置期望的函数允许被任意次调用,而一旦设置了.Times(0),任何一次调用都会立刻触发失败报告。
十、同一期望被报告两次"未满足",是不是冗余输出?
不是。gmock 每检测到一次失败,都会打印当时的完整相关信息(mock 函数参数、相关期望的状态等)。如果后续又检测到另一次失败,它会再次打印同样的上下文。
当两次失败之间某个期望的状态并未变化时,你确实会看到相同的状态描述出现两次——但它们对应的是不同的时间点。两次状态"恰好相同"本身就是一条有价值的信息(说明这段时间里该期望没有被触碰、也没被满足),这正是诊断问题时需要关注的线索。
十一、用 mock 对象时堆检查失败,用真实对象却正常?
先检查:你 mock 的那个类(最好是纯接口)有没有 virtual 析构函数?
只要从基类派生,就必须确保基类析构函数是virtual,否则会发生"坏事情"。看这个例子:
class Base { public: // Not virtual, but should be. ~Base() { ... } ... }; class Derived : public Base { public: ... private: std::string value_; }; ... Base* p = new Derived; ... delete p; // Surprise! ~Base() will be called, but ~Derived() will not // - value_ is leaked.把~Base()改为 virtual 后,delete p会正确触发~Derived(),成员(如value_)得以释放,堆检查器自然就满意了。
十二、"新期望覆盖旧期望"规则太别扭?——先理解 gmock 的匹配哲学
用户常见的抱怨代码:
// foo.Bar() should be called twice, return 1 the first time, and return // 2 the second time. However, I have to write the expectations in the // reverse order. This sucks big time!!! EXPECT_CALL(foo, Bar()) .WillOnce(Return(2)) .RetiresOnSaturation(); EXPECT_CALL(foo, Bar()) .WillOnce(Return(1)) .RetiresOnSaturation();官方回应:这不是 gmock 的缺陷,而是你没有选对表达测试意图的方式。gmock(以及 jMock)的根本哲学是:默认不要求期望按任何特定顺序匹配;如果你需要顺序,必须显式声明。这样设计的目的是让"过度指定(over-specify)测试"这件事变难,防止测试因实现细节被意外写死。
对于上面的需求,有两种更自然的写法。
写法一:用 InSequence 显式排序(期望按自然顺序书写):
// foo.Bar() should be called twice, return 1 the first time, and return // 2 the second time. Using a sequence, we can write the expectations // in their natural order. { InSequence s; EXPECT_CALL(foo, Bar()) .WillOnce(Return(1)) .RetiresOnSaturation(); EXPECT_CALL(foo, Bar()) .WillOnce(Return(2)) .RetiresOnSaturation(); }写法二:把动作序列放在同一条期望里:
// foo.Bar() should be called twice, return 1 the first time, and return // 2 the second time. EXPECT_CALL(foo, Bar()) .WillOnce(Return(1)) .WillOnce(Return(2)) .RetiresOnSaturation();为什么 gmock 要从后往前搜索期望(及 ON_CALL)?因为这样允许用户先为通用场景设定默认行为(比如在 mock 构造器或测试夹具的 SetUp 阶段),后面再用更具体的规则覆盖。如果改为从前向后搜索,这个极其有用的"通用默认 + 局部定制"模式就不可能实现。
十三、只设了 ON_CALL 没设 EXPECT_CALL,调用时仍打印警告?
gmock 在"整洁"与"安全"之间坚定选择后者。ON_CALL通常写在 mock 构造器或SetUp()里,作为测试间不变的行为默认值;而每条测试的期望则各不相同。在 setup 阶段有 ON_CALL 不代表这些调用是"被期望的"——如果根本没有EXPECT_CALL而方法被调用,很可能是个错误;此时 gmock 若默默放行,bug 就会悄悄溜进代码库。
如果你确认这些调用是合法的,请把:
ON_CALL(foo, Bar(_)) .WillByDefault(...);改成等价但"声明了期望"的形式:
EXPECT_CALL(foo, Bar(_)) .WillRepeatedly(...);这样 gmock 就知道你确实期望这些调用,不再打印警告。此外也可以用--gmock_verbose控制整体输出级别,调试时嫌吵就选更低级别的 verbosity。
十四、如何在 action 里删除(delete)mock 函数的参数?
如果 gmock 内置 action 满足不了"删除参数"这类自定义副作用,官方给出三条路:
- 用
MakeAction()自定义单态 action; - 用
MakePolymorphicAction()自定义多态 action; - 写一个 stub 函数,用
Invoke()调用它。
三者各自适用场景:绑定具体函数签名时Invoke()最省事;要跨多种签名复用时MakePolymorphicAction()最方便;需要对可用类型做精确控制时实现ActionInterface。详见 CookBook 的 "Writing New Actions"、"Writing New Polymorphic Actions" 与 "Using Functions/Methods/Functors" 章节。
十五、MOCK_METHODn 的第二参数为什么长这样?
MOCK_METHODn的签名是MOCK_METHODn(Method, return_type(args)),第二参数用函数类型(返回类型 + 括号包裹的参数列表)描述方法。有人建议改用MOCK_METHODn(Method, return_type, arg_1, ..., arg_n)语法,官方用几个实际优势回应了这种质疑。
反例:mock 一个以 map 为参数的方法:
virtual int GetSize(const map<int, std::string>& m);如果采用建议语法:
MOCK_METHOD1(GetSize, int, const map<int, std::string>& m);编译器会把const map<int, std::string>& m解析成两个参数而不是一个,直接编译失败。虽然可以用typedef起别名绕过,但那是给用户添堵。gmock 的语法把参数类型保护在一对括号内,天然规避了逗号歧义:
// This compiles fine. MOCK_METHOD1(GetSize, int(const map<int, std::string>& m));只有当返回类型包含未受保护的逗号时才需要typedef,而这种情形要罕见得多。
其他优势还包括:
MOCK_METHOD1(Foo, int, bool)会让读者困惑方法到底返回int还是bool,gmock 语法没有这种歧义;- 函数类型语法并非新发明——C 语言就在用,TR1 的
function库也大量使用,而 TR1 即将并入新版 STL,与之保持一致很稳妥; - 函数类型语法还贯穿 gmock 的其他 API(如 action 接口),用户想用高级特性迟早要学,不如从
MOCK_METHOD*就统一起来。
从源码看,MOCK_METHOD1到MOCK_METHOD10系列宏(含_T、_WITH_CALLTYPE变体)均定义于 gmock-generated-function-mockers.h,全部采用GMOCK_METHODn_(...)展开模式,参数类型正是包裹在括号中的函数类型。
十六、能 mock 静态/全局函数吗?
可以,但需要改造代码。gmock 提醒:一旦发现自己不得不 mock 静态函数,通常是模块耦合过紧的信号(灵活性、可复用性、可测试性都在变差)。更好的做法是:定义一个小的接口,让被测代码通过该接口调用原函数,然后 mock 这个接口。虽然初始投入多一点,但通常很快就能收回成本——这正是"面向接口而非面向实现编程"在测试维度上的落地。
十七、mock 需要做复杂的事情,指定 action 太痛苦,gmock 真差劲!
官方承认这不是一个问题,但免费附赠一个答案:你可能用错了工具。
- 不用 mock 的测试通常被称为state-based testing(基于状态的测试):执行代码,然后断言返回值正确或系统处于期望状态;
- mock 擅长的是interaction-based testing(基于交互的测试):不在最后检查系统状态,而是实时验证对象是否以正确的方式被调用,错误一出现就立刻报告,让你精确定位触发错误的上下文——这通常比状态测试更高效、更经济。
如果你的测试本质是 state-based,只是需要一个替身来模拟真实对象,那么你需要的其实是fake(假对象),而不是 mock。用 mock 去做复杂动作恰恰是它的短板,自然会觉得痛苦。结论:不是 mock 差,而是没选对工具,或者你在试图解决错误的问题。
十八、收到 "Uninteresting function call encountered - default action taken.." 警告,要慌吗?
完全不用,这只是 FYI(供你参考)。
它的含义是:某个 mock 函数没有设置任何期望(按 gmock 规则,这表示你对它的调用"不感兴趣",允许被调用任意次数),而它确实被调用了——这本身没问题,你并没有声明"禁止调用"。
但如果你本意是不允许该函数被调用,只是忘了写EXPECT_CALL(foo, Bar()).Times(0),那 gmock 打印这则提示就是在帮你抓这个疏漏。所以:看到这条消息时,先判断"这里是否本不该有调用",如果是,就去补上.Times(0)。为了帮你判断,gmock 打印这条消息时会顺带输出函数名和参数,方便定位是哪次调用。
十九、自定义 action:用 Invoke() 还是实现 action 接口?
两者皆可,选更顺手的那一个:
- action 只服务于某一种特定函数类型时,用
Invoke()更容易; - action 要复用于多种不同签名的函数(例如自定义一个类似
Return(value)的通用 action)时,MakePolymorphicAction()最简单; - 需要精确控制action 能用于哪些函数类型时,实现
ActionInterface是正途。
官方还给出了一个现成范本:Return()的实现就在 include/gmock/gmock-actions.h 中,写自定义 action 前不妨先读一读它的源码结构。
二十、SetArgPointee 报 "conflicting return type specified" 是什么问题?
这个错误表示:gmock不知道该给 mock 方法返回什么值。SetArgPointee()只描述了副作用(把某个指针参数指向的对象改成什么),并没有指定返回值。解决方法是把它和Return()用DoAll()串联起来:
// 同时完成“设置指针参数指向的内容”和“返回指定值”两个动作 EXPECT_CALL(foo, Bar(_)) .WillOnce(DoAll(SetArgPointee<0>(some_value), Return(true)));(示意:SetArgPointee<0>作用于第 0 个参数。)完整示例见 CookBook 的 "Mocking Side Effects" 章节。
二十一、问题不在这份 FAQ 里?——按图索骥继续查
FAQ 之外还有这些资源:
- 阅读仓库内的其他 wiki 文档:ForDummies(入门)、CookBook(配方大全)、CheatSheet(速查表)、KnownIssues(已知问题);
- 直接研读 gmock 头文件 与 测试代码——例如 gmock-matchers_test.cc、gmock-spec-builders_test.cc 展示了几乎所有内建 matcher 与期望语法的正确用法,是比任何文档都权威的"活教材"。
提问时请尽量提供以下信息(信息不足时没人能帮到你):
- 所用 gmock 的版本(或从 SVN 检出的修订号)——gmock 活跃开发中,你的问题可能已被新版修复;
- 操作系统;
- 编译器名称与版本;
- 传给编译器的完整命令行参数;
- 完整的编译器错误输出(若问题涉及编译);
- 出问题的实际代码(最好是能复现问题的最小完整程序)。
结语:FAQ 背后的 gmock 设计观
回看这份 FAQ,gmock 的每个"规定"背后都有一套自洽的设计哲学:默认不排序期望是为了防止过度指定;从后向前搜索期望是为了支持"通用默认 + 局部覆盖";保留 uninteresting-call 警告是为了在整洁与安全之间偏向安全;强制MatchAndExplain统一契约是为了让 matcher 的解释信息既丰富又不拖慢不需要解释的快速路径。理解这些动机,再配合本仓库 v8_6_7/testing/gmock/ 下的源码与测试用例交叉验证,你就能在 miniblink49(乃至任何 C++ 项目)里把 gmock 用得既正确又优雅。
【免费下载链接】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),仅供参考