美文网首页
iOS App Mock方案

iOS App Mock方案

作者: 小怪兽鱼小宝 | 来源:发表于2018-04-25 20:42 被阅读0次

    一.为什么需要Mock

    在app开发过程中,有时候server端并未开发完成,接口未能实现,这个时候又要求客户端同步进行开发;另外,就算接口已经开放完成,但是返回的数据,也未必能满足移动端的测试要求;如何破?1.可以通过hardcode来临时解决,但是这个很容易出错,在正式发版的时候还需要找到所有的hardcode位置改过来,否则后果严重啊。2.还有更“高级”一点,通过设置代理,用抓包工具修改网络数据,但这种效率低得令人抓狂。
    引入一个可以模拟网络请求的工具似乎就可以轻松满足需求,但实践证明,“模拟网络请求”这个需求并不简单。例如对于全新的业务,后台如果还没有数据,前端完全可以根据协议自己制造假数据返回。但是,很多情况下,可能是对已有业务的变更,也就是需要修改后台已有的业务数据。

    业界解决方案

    为了满足开发过程中模拟网络请求的需求,HttpMock 工具应运而生,目前业界已经有许多不同的实现方式,基本可以分为两类:

    自建HTTP Server

    可以在本地搭建 HTTP Server 模拟返回客户端所需要的数据。以 hibri/HttpMock 为例,它就是在本地搭建了一个HTTP Mock Server,然后根据需求返回指定数据。对于不需要模拟的请求,直接到达真实的Server,需要模拟的请求就转向MockServer。
    iOS 上目前应用比较广泛的是 OHHTTPStubs 和 Nocilla ,这两种实现的功能都类似。Nocilla选择用领域专用语言(DSL)的形式创建模拟请求,更容易理解,但是mock的功能需要应用中主动开启和关闭,一旦开启或关闭会影响应用中所有的HTTP请求。OHHTTPStubs 安装后自动启动,根据 request 自动判断是否需要截获。但目前这些开源库都未能做到灵活修改网络返回的数据。

    GYHttpMock 优势

    GYHttpMock 采用客户端截获的方式,在 Nocilla DSL 特性基础上,同时学习OHHTTPStubs的自动开启和识别,实现了 http response 的部分替换功能。
    具体优势:
    支持部分替换 HTTP Response,也就是可以修改真实网络返回的数据,这是相对于其它 HttpMock 独有的核心功能。客户端引入 GYHttpMock 后,只需一行代码就可以截获指定请求,并返回所需要的数据。不需服务端支持,也不需要建立本地HTTP Server。支持 NSURLConnection, NSURLSession,AFNetworking 以及所有采用 iOS Cocoa URL 加载方式的网络框架。支持正则匹配 HTTP Request,这样一条 httpMock 可以同时支持多个请求。mocked response 支持 json 内容的文件。一般情况下,mocked response 直接用 NSString 表达会比较清晰,但是返回内容比较多的情况下,因为转义符的原因,将内容以 json 格式写入文件会更容易些。使用安装
    直接将 GYHttpMock 的源文件加入项目中即可。也可以通过 CocoaPods 的方式接入。

    应用

    在需要拦截的请求之前创建正确的mockRequest:
    1.创建一个最简单的 mockRequest。截获应用中访问 www.weread.com 的 get 请求,并返回一个 response body为空的数据。

    mockRequest(@"GET", @"http://www.weread.com"); 
    

    2.创建一个拦截条件更复杂的 mockRequest。截获应用中 url 包含 weread.com,而且包含了 name=abc 的参数

    mockRequest(@"GET", @"(.*?)weread.com(.*?)".regex). 
    withBody(@"{/"name/":/"abc/"}".regex); 
    

    3.创建一个指定返回数据的 mockRequest。withBody的值也可以是某个 xxx.json 文件,不过这个 json 文件需要加入到项目中。

    mockRequest(@"POST", @"http://www.weread.com"). withBody(@"{/"name/":/"abc/"}".regex); 
    andReturn(200). withBody(@"{/"key/":/"value/"}"); 
    

    经测试,这个表示好像有问题。没有通过编译。
    4.创建一个修改部分返回数据的 mockRequest。这里会根据 weread.json 的内容修改正常网络返回的数据

    mockRequest(@"POST", @"http://www.weread.com"). 
    isUpdatePartResponseBody(YES). 
    withBody(@"{/"name/":/"abc/"}".regex); 
    andReturn(200). 
    withBody(@“weread.json"); 
    

    下面的代码经过测试,a1.json要放到.app包里

    mockRequest(@"GET", @"https://www.baidu.com").withBody(@"").andReturn(200).withBody(@"a1.json");
    

    假设正常网络返回的原始数据是这样:

    {
        "data": [{
            "bookId": "0000001",
            "updated": [{
                    "chapterIdx": 1,
                    "title": "序言"
                },
                {
                    "chapterIdx": 2,
                    "title": "第2章"
                }
            ]
        }]
    }
    

    weread.json 的内容是这样:

    {
        "data": [{
            "updated": [{
                "hello": "world"
            }]
        }]
    }
    

    修改后的数据就会就成这样:

    {
        "data": [{
            "bookId": "0000001",
            "updated": [{
                    "chapterIdx": 1,
                    "title": "序言",
                    "hello": "world"
                },
                {
                    "chapterIdx": 2,
                    "title": "第2章",
                    "hello": "world"
                }
            ]
        }]
    }
    

    GYHttpMock会根据 weread.json 指定的层次结构来修改原始数据,前提是 wearied.json 的数据结构需要和正常的返回数据一致,否则会导致修改失败或者不可预知的错误。

    实现原理

    GYHttpMock的工作流程如下:


    2539a157062493c1393a14d056616604.jpg

    其核心实现主要包括request匹配、request拦截、response替换三个部分。

    request匹配

    用于判断应用中的某个HTTP Request是否应该被mock。判断的条件包括method、URL、Headers、Body,其中URL和Body都支持正规匹配的方式,一个httpMock可以同时匹配多个HTTP Request。

    request拦截

    request拦截是通过继承 NSURLProtocol 的子类来实现。 NSURLProtocol 是iOS URL网络加载中功能非常强大的一个类,官方文档也有说明 NSURLProtocol ,通过重写它的方法,可以重新定义系统网络加载行为。在此之前,对于 NSURLConnection 的网络请求,需要这样注册 NSURLProtocol 的子类 GYMockURLProtocol

    [NSURLProtocol registerClass:[GYMockURLProtocol class]]; 
    

    对于 NSURLSession 的网络请求,需要替换 protocolClasses 方法

    Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration"); 
    [self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]];
    

    最后,重点是重写 NSURLProtocol 类的 canInitWithRequest 和 startLoading 方法。 canInitWithRequest 是用于判断是否可以发起网络请求,可以通过这个过滤不在拦截范围内的request,不影响App的正常网络请求。 startLoading 是替换response数据的核心所在,成功截拦的request会进入该方法,在这个方法中替换或修改response数据,再回调给上层。

    response替换

    对于需要全部替换的response,实现方式是在 startLoading 方法中调中 NSURLProtocol 的 URLProtocol:didReceiveResponse:cacheStoragePolicy: 方法,将替换好的response回调给上层。对于需要部分替换的response,GYHttpMock会用NSURLConnection的方式,发起一次真正的网络请求,待数据回来后,再与mockRequest中的response数据进行合并,最后将合并后的数据回调上层。部分替换过程中遇到两个问题:
    部分替换时要发出一个真实网络请求拿到原始数据,这个请求按照之前的规则又会被NSURLProtocol截获,从而进入死循环。解决办法是,start request前将这个GYHttpRequest打上标记,表明是不需要再次截获的,等拿到reponse后再将GYHttpRequest上的标记去掉,避免死循环。
    两个response内容合并的问题。因为json的数据结构非常灵活,可以任意层次嵌套,如何指定修改或添加某个节点下的数据是比较困难的,尤其是json中数组的嵌套,导致要指定修改数组中某个位置的元素变得非常困难。GYHttpMock采用的方式是,在mockRequest的response中指出需要修改的节点完整位置,然后用这个数据结构去匹配目标数据(具体算法请查看 GYHttpMock源码 ,好处在于可以支持比较复杂的数据结构,但这就要求使用者对目标数据结构非常清楚。

    GYHttpMock已经在 GitHub 开源,目前已用于 微信读书 项目中,使用过程如果有问题或者建议,欢迎提交 issue 和 pull request。

    Demo稍后放到Github上面

    相关文章

      网友评论

          本文标题:iOS App Mock方案

          本文链接:https://www.haomeiwen.com/subject/yxpelftx.html