ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Apache Weex 内嵌 Google Mock 设计文档解读:ACTION* 与 MATCHER* 宏的架构设计与实践

Apache Weex 内嵌 Google Mock 设计文档解读:ACTION* 与 MATCHER* 宏的架构设计与实践 移动开发跨平台原生移动前端【免费下载链接】incubator-weexApache Weex (Incubating)项目地址https://gitcode.com/gh_mirrors/in/incubator-weex点击查看免费下载导读本文基于 Apache WeexIncubating仓库中随附的 Google Mock 设计文档 DesignDoc.md系统讲解 Google Mock 用于零样板自定义 Action桩行为与 Matcher参数匹配器的宏体系设计从ACTION(name)、ACTION_P(name, param)到ACTION_P9以及对应的MATCHER*宏族。读者将理解这些宏要解决的 C 测试痛点、其预定义符号与类型推断机制、参数化与重载规则、类型约束技巧以及它们在当前仓库 googlemock 中从设计提案到 gmock-generated-actions.h 与 gmock-generated-matchers.h 落地实现的全过程。Google Mock 是 Google 的 C 测试框架 README.md 明确说明的 C 单元测试配套方案被 Weex 的 C 核心weex_core测试体系随 googletest 一并引入用于为跨平台原生代码编写 mock 与断言。一、设计动机C 缺乏闭包导致的 Action 定义之痛设计文档开篇就点明核心问题由于 C 标准当时缺乏闭包closure机制在 Google Mock 中定义一个自定义 Action 需要付出不小的代价。假设你想实现递增 mock 函数第二个参数所指向的值并返回它传统方式需要先写一个普通函数再用Invoke包装int IncrementArg1(Unused, int* p, Unused) { return (*p); } ... WillOnce(Invoke(IncrementArg1));设计文档指出这种方式存在三个明显的不足冗余的占位参数即便该 Action 只关心 mock 函数的第二个参数定义时仍必须把其余参数全部列出用Unused占位非常繁琐参数个数被锁死这样定义的 Action 只能用在恰好有 3 个参数的 mock 函数上复用性差调用语法不自然使用者必须写Invoke(IncrementArg1)而不是更直观的IncrementArg1()。1.1MakePolymorphicAction()的救赎与代价文档进一步说明后两个问题可以通过MakePolymorphicAction()解决但代价是大量样板代码class IncrementArg1Action { public: template typename Result, typename ArgumentTuple Result Perform(const ArgumentTuple args) const { return (*tr1::get1(args)); } }; PolymorphicActionIncrementArg1Action IncrementArg1() { return MakePolymorphicAction(IncrementArg1Action()); } ... WillOnce(IncrementArg1());可以看到虽然现在可以像调用函数一样使用IncrementArg1()但使用者被迫手写一个模板类、实现Perform成员函数并手工解析参数元组tr1::get1(args)。设计文档给出的目标是让用户以 C 所能允许的最小样板代码量来定义自定义 Action。二、核心方案ACTION(name)宏设计文档提出引入一个新宏来消灭样板代码ACTION(name) { statements; }在命名空间作用域namespace scope中使用它就会定义一个名为name的 Action。宏体内部可以引用 mock 函数的第 K 个0 起始参数符号名为argK。前述递增第二个参数并返回的例子可以改写为ACTION(IncrementArg1) { return (*arg1); }之后即可直接使用... WillOnce(IncrementArg1());2.1 简洁是设计目标类型安全是底线设计文档强调使用ACTION宏时不需要指定 mock 函数参数的类型——简洁是首要设计目标。但这并不意味着失去类型安全如果*arg1不支持运算符编译器会报错如果(*arg1)的类型与 mock 函数返回类型不兼容编译器同样会报错。即类型由编译器推断类型错误在编译期暴露而非运行期。一个更综合的示例展示宏体内多语句能力ACTION(Foo) { (*arg2)(5); // 以 5 调用第 2 个参数函数指针 Blah(); // 调用普通函数 Blah() *arg1 0; // 把第 1 个参数指向的值设为 0 return arg0; // 返回第 0 个参数 }2.2 预定义符号表为方便与灵活ACTION宏体内部还提供了以下预定义符号设计文档原文表格符号含义argK_typemock 函数第 K 个0 起始参数的类型argsmock 函数的全部参数以元组tuple形式呈现args_typemock 函数全部参数组成的元组类型return_typemock 函数的返回类型function_typemock 函数的类型设计文档以一个桩 Action 示例int DoSomething(bool flag, int* ptr);给出了完整的符号绑定对照表预定义符号绑定值/类型arg0flag的值arg0_typebool类型arg1ptr的值arg1_typeint*类型args元组(flag, ptr)args_typestd::tr1::tuplebool, int*类型return_typeint类型function_typeint(bool, int*)类型注意args_type中出现的tr1::tuple是文档写作时的 C 标准库形态std::tr1当前仓库实现已演进为 C11 风格见下文源码实现印证。三、参数化 ActionACTION_P与ACTION_P2~ACTION_P9很多时候 Action 需要携带参数。设计文档提出第二个宏ACTION_P(name, param) { statements; }例如ACTION_P(Add, n) { return arg0 n; }即可写出// 返回参数 #0 5。 ... WillOnce(Add(5));3.1 两个容易混淆的术语设计文档在此处特意定义了术语避免混淆arguments实参调用 mock 函数时传入的值parameters参数实例化构造某个 Action 时传入的值。Add中的n是参数parameterarg0则来自 mock 函数的实参argument。与ACTION一样ACTION_P也无需声明参数类型——编译器会自动推断。若参数名为param还可以用 Google Mock 预定义的符号param_type引用其被推断出的类型。3.2 多参数版本为支持多参数 Action设计文档明确将提供ACTION_P2、ACTION_P3……以此类推。示例ACTION_P2(ReturnDistanceTo, x, y) { double dx arg0 - x; double dy arg1 - y; return sqrt(dx*dx dy*dy); }使用... WillOnce(ReturnDistanceTo(5.0, 26.5));文档还给出一个重要视角ACTION可以看作参数个数为 0 的参数化 Action 的退化形式——这为下面统一的类型命名规则埋下伏笔。四、高级用法4.1 按参数个数重载 Action可以轻松定义按参数个数重载的 ActionACTION_P(Plus, a) { ... } ACTION_P2(Plus, a, b) { ... }两个Plus宏分别生成不同后缀的类PlusActionP与PlusActionP2因此互不冲突。4.2 限制参数或实参的类型为了最大限度的简洁与可复用ACTION*宏族刻意不允许直接声明 mock 函数实参与 Action 参数的类型而把类型推断交给编译器。若确实需要显式约束类型设计文档给出几种技巧ACTION(Foo) { // 强制 arg0 可转换为 int int n arg0; ... use n instead of arg0 here ... } ACTION_P(Bar, param) { // 强制 arg1 的类型为 const char* ::testing::StaticAssertTypeEqconst char*, arg1_type(); // 强制 param 可转换为 bool bool flag param; }其中StaticAssertTypeEq是计划加入 Google Test 的编译期断言命名与 C0x 的static_assert对齐。4.3 Action 对象类型命名规则若你写的函数需要返回一个ACTION对象就必须知道它的类型。设计文档给出了简洁的命名规则与参数个数一一对应定义形式表达式对象类型ACTION(Foo)Foo()FooActionACTION_P(Bar, param)Bar(int_value)BarActionPintACTION_P2(Baz, p1, p2)Baz(bool_value, int_value)BazActionP2bool, int.........后缀规律Action0 参、ActionP1 参、ActionP22 参……必须为不同参数个数的 Action 挑选不同后缀否则无法按参数个数实现重载。五、什么时候该用什么时候不该用设计文档专门给出了使用建议章节提醒用户宏并非万能ACTION*宏非常方便但如果你需要大量复用某个 Action请同时考虑其他实现手段如ActionInterface或MakePolymorphicAction()其他方式虽然工作量大但能对 mock 函数实参与 Action 参数的类型施加更细粒度的控制通常能产生更好的编译器报错信息长期看收益更大它们还允许基于参数类型重载 Action而不仅仅基于参数个数。简言之宏换来了简洁也让出了类型控制力——这是一条明确的设计权衡。六、相关工作为什么不用 Boost Lambda 库设计文档敏锐地指出ACTION*宏实质上是在模仿闭包lambda 表达式 / 匿名函数两者目标都是降低定义函数时的语法开销。C0x 将原生支持 lambda但文档写作时 lambda 尚未进入 C 标准当时一些非标准库最典型的是BLL即 Boost Lambda Library试图缓解此问题但设计文档认为它们不适合用来定义 Action理由如下非标准且安装不普遍Google Mock 只依赖标准库与tr1::tuple属于新 C 标准、gcc 4 自带希望保持这一纯净依赖学习成本不低BLL 并不易学会被 C0x lambda 淘汰不愿让用户依赖一个行将消亡的库基于操作符、过于临时无法书写语句块也无法把 lambda 参数传给函数语义微妙、易迷惑新手例如表达式_1 foo中foo在表达式求值时只自增一次而_1在每次调用匿名函数时都会自增——远非直观。ACTION*宏族则完全规避了上述所有问题。七、未来改进方向设计文档在文末记录了三条演进设想体现了作者对 C 标准演进的预判组合ACTION*即在一个ACTION*内部调用另一个ACTION。作者并不确定是否需要因为把ACTION定义放进函数模板再组合函数模板可达到类似效果将根据用户反馈再定支持在函数体内使用ACTION*()当时 C 标准不允许用函数局部类型function-local types实例化模板因此ACTION*()只能出现在命名空间作用域。C0x 将解除此限制届时可重新审视实现支持 lambda 作为 ActionC0x 引入 lambda 后可能会支持直接以 lambda 充当 Action。仓库实现印证在 gmock-generated-matchers.h 的文件头注释中仍保留着MATCHER*()只能用于命名空间作用域原因是 C 尚不允许用函数局部类型实例化模板C0x 将修复此问题届时再考虑支持在函数内使用的说明——设计文档的这条限制原样延续到了实现代码里。八、姊妹方案MATCHER*宏的设计设计文档在 Action 宏之后规划了同样思路的匹配器宏MATCHER(name) { statements; }宏体内可用arg引用被匹配的值。例如MATCHER(IsPositive) { return arg 0; }之后IsPositive()即成为一个匹配器当且仅当被匹配值大于 0 时匹配成功。设计文档同时规划了MATCHER_P、MATCHER_P2……等参数化版本。九、源码实现印证从设计提案到落地宏设计文档是提案而当前仓库保留了该设计的最终实现可直接对照阅读验证每一条设计决策的落地形态。9.1 ACTION 宏族的实现在 gmock-generated-actions.h 中可以依次找到ACTION(name)生成name##Action类 内联工厂函数name()。内部定义嵌套类gmock_ImplF它继承::testing::ActionInterfaceF并提供function_type、return_type、args_type三个 typedef——正是设计文档第 2.2 节符号表的实现来源同时声明了gmock_PerformImpl参数列表从arg0_type一直到arg9_type支持最多 10 个实参ACTION_P(name, p0)生成模板类name##ActionPp0_type用p0_type承接被推断的参数类型类中保存成员p0并提供operator ::testing::ActionF()隐式转换ACTION_P2(name, p0, p1) 及 ACTION_P3、ACTION_P4、ACTION_P5、ACTION_P6、ACTION_P7、ACTION_P8、ACTION_P9逐级增加模板参数直到ACTION_P99 个参数与设计文档提供ACTION_P2、ACTION_P3等的规划完全一致。关键印证点宏生成的类名后缀Action/ActionP/ActionP2……与设计文档第 4.3 节的命名规则表格逐字吻合可见实现严格遵循了设计提案。9.2 MATCHER 宏族的实现在 gmock-generated-matchers.h 中MATCHER(name, description)生成name##Matcher类嵌套gmock_Implarg_type继承::testing::MatcherInterfaceGTEST_REFERENCE_TO_CONST_(arg_type)实现MatchAndExplain、DescribeTo、DescribeNegationTo三个虚函数MATCHER_P(name, p0, description)模板类name##MatcherPp0_type依次提供 MATCHER_P2 直到 MATCHER_P10最多 10 个参数。与设计文档的一处可见差异最终实现中每个 MATCHER 宏都额外带有一个description参数——在宏体返回空描述时实现会用FormatMatcherDescription根据名称与参数自动生成人类可读的匹配器描述用于失败信息输出。这正是设计提案在落地过程中被完善的典型例证也从侧面说明DescribeTo/DescribeNegationTo需要稳定可靠的文本来源。9.3 在 Weex 仓库中的定位当前仓库 googlemock 位于weex_core/test/third_party/googletest/下与 googletest 本体同属weex_coreC 测试基础设施的第三方依赖。在 googlemock/README.md 中Google Mock 的能力被概括为用简单宏轻松创建 mock 类、提供丰富的 matcher 与 action、支持无序/部分有序/完全有序的期望约束、且可由用户自行扩展——其中用宏轻松创建 mock与由用户扩展新 matcher 与 action两点正是本文所述ACTION*/MATCHER*宏设计所提供的核心能力。该 README 还建议新用户按Google Test 基础 → ForDummies → 构建说明的顺序入门而 DesignDoc.md 则面向希望理解宏机制内部设计的开发者。十、总结一份设计文档的完整生命周期回顾全文DesignDoc.md 完整走过了从问题定义 → 方案设计 → 细节规范 → 权衡说明 → 演进规划的设计闭环问题C 缺乏闭包自定义 Action 样板代码过多Invoke方案限制参数个数、语法不自然方案ACTION*宏族以最小样板定义 ActionargK/args/return_type等预定义符号与编译器类型推断保证简洁且类型安全扩展ACTION_P~ACTION_P9支持参数化按参数个数重载、param_type/StaticAssertTypeEq类型约束、统一的对象类型命名规则权衡宏简单但放弃类型控制重用途场景建议改用ActionInterface/MakePolymorphicAction()对照相对 BLL 等非标准库的五大优势以及基于 C0x 演进lambda、函数局部类型、组合的未来规划落地上述设计最终在 gmock-generated-actions.h 与 gmock-generated-matchers.h 中原样实现含MATCHER宏额外引入description参数这一实现期优化并为 Weex 的 C 核心测试所携带。对于任何希望深入 C 测试框架内部、理解如何用宏在无闭包语言中优雅模拟闭包的开发者这份文档与仓库内实现构成了一个完整的、可对照研读的案例。赞分享移动开发跨平台原生移动前端【免费下载链接】incubator-weexApache Weex (Incubating)项目地址https://gitcode.com/gh_mirrors/in/incubator-weex点击查看免费下载相关推荐基于 v8 内嵌 Google Mock 的 ACTION/MATCHER 自定义宏设计从 DesignDoc 到源码实现的全解基于 v8 内嵌 Google Mock 的 ACTION/MATCHER 自定义宏设计从 DesignDoc 到源码实现的全解 本文以 V8 引擎测试栈中内前端桌面应用MiniBlink49 内置 Google Mock ACTION* 自定义动作宏设计解析v8_6_7 测试栈中的 Action/Matcher 宏机制MiniBlink49 内置 Google Mock ACTION 自定义动作宏设计解析v8_6_7 测试栈中的 Action/Matcher 宏机制 本文以前端桌面应用miniblink49 中 v8_5_7 测试栈的 Google Mock 设计文档深度解读ACTION/MATCHER 宏如何在 C03 里造出lambdaminiblink49 中 v8_5_7 测试栈的 Google Mock 设计文档深度解读ACTION/MATCHER 宏如何在 C03 里造出lam前端桌面应用上一篇三分钟搞定B站缓存视频m4s转MP4的傻瓜式完整教程下一篇fumadocs-python 指南为 Fumadocs 一键生成 Python API 文档运行时内容源与构建期 MDX 转换创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表