WCCgiMock——客户端模拟网络回包工具介绍

2,376 阅读12分钟

文本转载自我的博客:littleliang.xyz/2019/09/18/…

一、 为什么需要CgiMock工具

在敏捷开发的过程中,客户端与后台开发通常是同时进行的,经常会出现客户端需要等待后台开发完成才能验证客户端逻辑的情况。等待联调往往会成为阻塞项目进展的瓶颈。Cgimock工具的作用就是解除客户端对后台的依赖,解决数据源的问题,提升客户端开发和调试的效率。

以往的的产品决策流程需要等待后台开发完成,上到测试服务器上,才能让产品经理体验。有了cgimock工具,可以用模拟数据代替后台数据。快速输出产品原型,降低试错的时间成本。

二、 CgiMock工具是什么

CgiMock工具可以帮助开发者在不侵入代码的情况下,构造模拟数据,模拟网络回包。它的工作模式分为两种:

第一种是在Cgi请求阶段拦截请求,返回预设好的模拟数据给业务层。适用于后台还没有数据返回的全新业务场景。

image-20190918122820040

第二种模式是,在请求阶段不拦截,等到后台返回回包后,将模拟数据与后台回包进行合并,再返回给业务层。一般应用于对已有业务的变更。

image-20190918122820040

业界对于CgiMock的实现方式也分为两类,一类是搭建MockServer,在服务器端拦截请求。第二种是终端CgiMock,在客户端上拦截请求。MockServer方案的优势是与平台无关,天然支持跨平台。但该方案有以下的缺点:首先,如果要支持模式二需要将MockServer与后台服务器对接,有额外的开发工作。其次,MockServer需要解析客户端的请求包,所以需要支持客户端使用的数据交换格式,通用性不好。最后,搭建和维护MockServer也有一定的成本。

而终端CgiMock由于是在客户端上拦截请求,所以适配模式2非常容易,也不需要考虑客户端的数据交换格式,通用性更好。但是如果想要实现跨平台,需要在不同平台上都实现Mock组件。

权衡了两者的优缺点,我认为终端CgiMock方案的通用性与灵活性更好,使用者无需做太多额外的工作。所以在技术选型上,我选择了终端CgiMock的解决方案。

随后,我调研了一些业界关注度较高的终端CgiMock开源项目,包括阿里的OHHttpstubs以及广研的GYHttpMock。得出了以下比较结果,OHHttpstubs作为一款业界使用最多的终端cgimock开源工具,实际上只是满足了模拟网络回包的基本需求。

首先,它的mock代码是用原生语言写的,不支持跨平台。第二,不支持后台数据与模拟数据合并。第三,不支持Mock用例的组织,只能够以Cgi为单位的mock。第四,不支持热更新。最后,它只支持基于NSURLSesion网络库,像是微信这种使用自研网络组件的应用无法使用。GYHttpMock则在它的基础上增加了模式二的支持。以及支持从JSON文件中读取回包数据和正则表达式匹配。

比较过后,现有的开源工具并不满足我的需求。所以我决定设计一款功能更丰富,通用性与易用性更好,可以给项目中不同角色使用的CgiMock工具。我命名它为WCCgiMock。

三、 WCCgiMock工具介绍

3.1 WCCgiMock的编写语言选择

我遇到的第一个问题,就是需要选择一门合适的语言来编写CgiMock代码。为此,我定了三个需要符合的要求:

  1. 这门语言是支持跨平台的。使用者只用写一套Mock代码,就能在多个平台上运行。
  2. 需要是一门脚本语言,脚本语言是解释执行,意味着能够支持热更新,无需重新编译
  3. 需要对JSON格式原生支持。一是因为JSON格式是当前最常用的数据交换格式,学习门槛低。第二是因为无论是在iOS还是安卓,都有丰富的开源库将JSON格式转化为原生语言的Model类。

结合这三点的要求,我选择了JavaScript作为Mock代码的编写语言。JavaScript除了满足上述要求,在iOS和Android平台上,都有非常成熟的JSBinding技术,能让JS代码与原生代码相互通讯。

3.2 内部DSL(特定领域设计语言)设计

引入一门新的语言,肯定会带来一定的学习成本,关键是如何通过设计来最大程度地降低这个成本,WCCgiMock的目标是想做到让非开发人员,像是测试或者产品也能轻松上手。

为此,我设计了适用于JavaScript上的内部DSL语句来描述提供的功能。内部DSL,指的是利用某种编程语言的特性,将针对特定目的的处理用高级、简洁的方式进行描述。它的优点在于可以通过链式调用串联各个语句,接近自然语言,符合人的思维习惯。而且,DSL语句将处理过程高度抽象,使用者可以专注于目标,不需要理解过程的实现,这能够降低编写Mock代码的学习成本。

我将mock过程的步骤及参数配置,抽象为了若干个DSL语句。

mockRequest语句,用于声明一个CgiMock。声明后,每当这个cgi发起请求,都会根据随后设置的参数返回模拟数据。withRequest语句用于修改请求参数。andreturn与withresponse语录分别是设置回包的返回码与回包数据内容。updateFromSvr语句,设置是否与后台回包合并。

除了mockRequest语句外,其他语句都不是必须调用,使用者可以根据目的来自由组合语句

这里展示一个Mock脚本的例子:

可以看到,这种基于方法链的流畅风格代码,接口定义简洁,阅读起来与自然语言相似,非常容易理解。

3.3 响应式数据

除了使用DSL语句,WCCgiMock相比于其他开源工具还提供了响应式数据的能力,基于这个能力,Mock脚本可以模拟后台逻辑,根据请求内容来返回不同业务分支的模拟回包。

使用者可在脚本中定义不同分支的模拟回包,并实现serverLogic方法。当这个Cgi请求时,Native层会通过jsbridge回调这个方法,并传入请求的数据。ServerLogic可根据请求内容返回不同的回包给Native层。

相比于静态化的数据返回,动态化的响应式数据不需要每验证一个分支就要切换或是修改数据,调试起来更高效。

响应式数据,还能起到质量保障的作用。基于这个能力,后台开发可根据产品用例生成带有模拟后台逻辑的Mock用例给到客户端开发与测试工程师,客户端开发可根据模拟后台逻辑处理所有的业务分支,避免由于后台逻辑的不透明而导致某个分支没有处理的情况,减少客户端自身的逻辑异常。

对于测试来说,很大的一个痛点是,测试时需要询问需求涉及哪些cgi,协议改动了哪里,后台逻辑是什么。这些以往都需要与开发进行沟通才能获知。而现在根据Mock脚本就能了解到这些信息,快速地输出测试用例,节省了双方的沟通成本。测试工程师还可在用例上添加构造各种数据,特别是一些边界场景数据和异常数据。提升客户端对后台数据的容错能力。

产品也可以在后台未完成的情况下体验完整的产品流程,包括各个业务分支及错误处理。

讲一个真实的案例,年初龙哥提出春节要做拜年红包,当时时间非常紧迫,需要在很短时间内完成demo让老板体验,但是后台评估需要一周的开发时间。响应式数据的能力就起到了作用。利用响应式数据,我只用了两天时间就输出了Demo,让老板在没有感知到是模拟数据的情下体验了整个产品的流程,敲定了最终的方案,产品根据方案快速地调整了方向,使项目最终得以顺利上线。这个例子体现了响应式数据对加速产品决策起到了作用。

3.4 用例组织能力

WCCgiMock另一个核心能力是支持组织用例。业界上的工具是都是以单个Cgi为单位进行mock。但实际情况是,一个完整的业务流程通常是由多个模块,多个Cgi构成的。

举个例子,绑定银行卡作为一个通用的业务模块,经常被其他业务所使用,我们可以将绑卡流程单独写成一个Mock用例,其他业务使用时,直接引用即可。

用例的组织可以用一棵树来描述,Mock脚本是叶子节点,由DSL语句构成。UseCase是非叶子节点,由require语句构成,用于声明UseCase由哪些Mock脚本和其他UseCase构成。require语句会将目标脚本加载到JS环境中,并执行脚本中的DSL语句。

相比于以cgi为单位的mock,组织用例具有一下的优势

  1. 模块化,可复用
  2. 使用者不需要去理解其他业务用例的构成与实现
  3. 以用例为单位进行存储,易于管理。
  4. 层次分明,可清晰看到用例是由哪几个模块组成

3.5 真机环境下的热更新调试

在iOS模拟器和Android平台上,由于没有严格的沙盒机制,实现热更新比较简单。这里重点说一下iOS真机环境下的热更新解决方案。iOS真机环境由于沙盒机制的限制,无法将Mock脚本直接写入app沙盒中。我在组内的HotLoader工具基础上做了改动,实现了iOS真机环境下的热更新。

HotLoader工具基于usbmuxd服务和watchdog文件监控。usbmuxd服务可以将USB通信抽象为TCP通信。相当于app是Server端,mac设备是Client端。两端通过usb端口来传送数据。watchdog的作用是监听Mock用例文件的修改事件,当用例文件被修改时,kernel就会向watchdog发出通知,这时HotLoader会将Mock用例打包, 通过usb端口将用例传输到App端上,通知CgimockMgr重载脚本。

使用HotLoader,开发者不需要去做额外的操作,每当修改、保存用例后,工具会将修改后的Mock用例自动打包,发送到设备上,并重载生效。调试过程非常方便。

3.6 CgiMock脚本与原生代码通信

介绍完核心能力,接下来阐释一下WCCgiMock是如何实现JS代码与原生代码通信。

JS代码与原生代码通信的实现基于JSBinding技术。JSBinding引擎作为桥接层,在JavaScript代码与原生代码之间建立一座桥梁,使JS对象与原生对象相关联,双方可以互相调用。

该三层结构,对应于WCCgiMock的实现如下图所示

JS层中CgiMock.js文件定义了提供给Mock脚本调用的接口方法。包括声明方法mockRequest,用例组织方法require,日志打印方法。此外我还利用了JavaScript的原型链特性,为原型对象Object提供了一些工具函数,让使用者可以更方便地编写serverlogic代码。当WCCgiMock初始化时,会将CgiMock.js载入JS运行环境中

JSBinding层中,我在JSContext上注册了相应的桥接方法,提供给JS层调用。桥接方法会将传入的js数据类型转为原生数据类型,再去调用Native层中的具体实现。

在Native层中,CgiMockDSL类定义了所有DSL语句,并通过JSExport协议注册到JSContxt中。所有DSL方法都会返回CgiMockDSL类的实例对象,通过JSBinding引擎转换为JSObject,返回到JS环境中,所以在JS环境中就可以使用这个JSObject调用下一个DSL语句,从而实现链式调用。

这就是整个通讯过程的基本原理

3.7 整体架构

总结一下WCCgiMock的整体架构,

整体架构主要分为7个模块

  • CgiMock.js,为mock脚本提供接口。
  • Random.js负责解析随机数据占位符,并生成随机数据,数据占位符我稍后会介绍。
  • CgiMockJSEnv,提供JS环境,为JS层提供桥接方法和异常捕捉处理
  • CgiMockDSL, 提供DSL语句,并负责执行和解析。
  • CgiMockData,用于存储dsl语句传入的参数。
  • HotloaderServer,负责与Client端通信,接收打包的Mock数据,通知CgiMockMgr重载Mock用例。
  • YYModel则是一个开源框架,负责将JSON数据与原生Model数据相互转换

CgiMockMgr负责组织各个模块,并将模拟数据返回给网络层

如果想要扩展功能的话,只需要在JS层去做扩展就可以了。比如随机数据生成这个能力,以前是没有的。通过在js层进行扩展,只需要写一套代码就可以在多个平台上都实现这个功能。

四、总结

  • 目前

    WCCgiMock作为一套敏捷开发工具,能够起到提升开发效率、加速产品决策、保障客户端质量的作用,目前已经实现了iOS/Mac、Android端的客户端组件和配套的可视化工具链。WCCgiMock已在微信iOS客户端团队,测试组推广使用,支付团队推广使用,累积的Mock用例已达一百多个。

  • 未来

    WCCgiMock是否会开源?答案是肯定的。目前正在整理代码,很快会向公司提出开源申请,相信WCCgiMock很快就会与大家见面。