美文网首页Android
Mock你的数据,放个大招

Mock你的数据,放个大招

作者: MxsQ | 来源:发表于2019-02-09 10:10 被阅读503次

    前言

    本文提供一种实现Mock数据的解决方案,解决思路为核心,实现则以OkHttp3为例,可根据提供的思路将方案嵌入实际项目代码中。

    为何要Mock

    开发项目中,不管是等待API的完成,还是测试API正常、异常流是否达到预期,都需要相应的响应数据验证。但是等待他人提供API是痛苦的,Mock数据也痛苦的。

    常见解决方案

    达到目标可以有很多种路径,Mock方案也有很多种。常见的Mock方案有:

    硬编码
    比如有一个API需要测试某JSON数据,则将此String在回调处进行替换以达到目的。优点为操作方便,缺点如:

    • 调整需要重新编译
    • String过长可能无法完全复制
    • 如有多个API需要进行Mock,杂乱无章

    工具映射
    借助如Charles等工具可以将API等信息进行替换,优点为覆盖面广,可实时Mock。缺点为:

    • 有操作成本
    • 需要记住哪些API进行了Mock并在不需要时关闭功能

    本地仓库
    本地存有Mock数据文件,可网络层等合适位置对API进行查阅操作,命中时替换成Mock数据。优点为易维护,全面。缺点为:

    • 调整困难,需要重新打包
    • 可通过adb shell等方式动态替换数据文件,但操作有成本

    放个大招

    理想状态下,Mock应该满足以下要求:

    1. 不需要对已有程序进行改动,因为需要进行回退
    2. 易于管理API,Mock哪些API一目了然,可配置
    3. 实时操作,并且操作成本低

    综合以上需要,给出方案思路大致如图


    Mock方案.png

    图中表达如下

    1. 方案嵌入位置选在网络层中的拦截层,得益于拦截层得天独厚的位置,在拦截层中实现Mock方案最方便,也最透明。了解拦截层点我。本篇文章的实现也是基于OkHttp3的拦截链来进行的,当然,在实际项目中拥有自主网络框架的拦截层再好不过。如果没有拦截层,也可以参考这个思路,实现在合适的位置。因为网络请求一定会有某个公用的入口和出口,在此类位置进行即可。
    2. 需要理解的一点是,Mock不代表没有网络请求,对于Mock来说,最关心的是拿到Mock数据,从何而来倒不是核心。
    3. 本地会有一份配置表,配置表记录的哪些API需要进行Mock和Mock地址。这份配置表不需要使用文件存储,实现某个共享对象如单例存储即可。而配置表的获取可以选择合适的时机从远端同步,比如程序启动时、发生网络请求时做预检查。
    4. 网络请求会被拦截层间层,当API在配置表中命中,则这证明此API需要Mock,将实际请求重定向为配置表配置的Mock地址从远端拿到Mock数据即可,反之,API未命中则维持原先操作。
    5. 各个Mock地址则保存的合适数据,保证实时性。

    如此实现的原因则是综合了之前常见解决方案的优缺点:

    • 进行Mock不需要修改程序代码
    • 易于操作,需要进行Mock的API在远端配置即可
    • 配置更变即配置表有改动时,不需要重新打包,重启应用即可
    • 有实时性,在不需要进行Mock时,Debug环境下仅更改配置表即可
    • 更新Mock的API数据时,重新触发请求即可

    如上所诉,需要一个远端仓库存储Mock数据和配置表,如果没有后端条件或者没有合适方案,可以使用Easy-Mock 点我,文章会基于此进行实现,如何使用Easy-Mock参考官方文档即可。

    实现

    首先说明,实现部分,代码不多,仅摘取了核心代码进行解说,因为涉及到需要依赖具体环境进行的代码,则以一个函数进行表达,代码中会做详细注释。

    假设已拥有远端仓库,则配置表是这样子的

    {
      "open": "true",
      "reflects": [{
          "url": "www.baidu.com",
          "mockKey": "baidu"
        },
        {
          "url": "text",
          "mockKey": "test"
        }
      ]
    }
    

    open字段代表Mock方案是否启用,reflects是映射数据,其中url代表需要进行mock的API,mockKey则代表进行拼接的key。以Easy-Mock来说,会提供基本url并通过不同的key来访问具体的Mock数据,假设基本url为“www.test.com”,则当mockKey为test时,mock地址为“www.test.com/test”。

    而mock地址返回的数据,则视具体情况而定,比如下面一个简单的JSON。

    {
      "code": 200,
      "message": "success",
      "entry": "your data"
    }
    

    如果是使用了Easy-Mock的话,可以类似这样的仓库图。


    easy-mock api列表.jpg

    仓库准备完毕,以下为核心代码

    public class OKMockInterceptor implements Interceptor {
    
        @Override
        public Response intercept(Chain chain) throws IOException {
    
            // 获取原请求
            Request request = chain.request();
    
            // mock 条件,是具体情况而定
            if (isMock()){
    
                // 存储且管理远端配置表的数据,url检测也是通过此类检查
                MockWarehouse mockWarehouse = MockWarehouse.instance();
    
                /**
                 * 拉取配置表
                 */
                if (mockWarehouse.isInit()){
                    // 配置表地址
                    String mockConfigUrl = mockWarehouse.getConfigUrl();
                    // 拉取配置表的请求
                    Request configRequest = new Request.Builder()
                            .url(mockConfigUrl)
                            .build();
    
                    // 拿到配置表信息
                    Response configResponse = chain.proceed(configRequest);
                    
                    
                    // 检查Response状态以及Response结果
                    if (configResponse.code() == 200){
                        // 这里拿到response的string,
                        // 在okHttp3中,使用peekBody()而不是body()获取ResponseBody,因为后者会导致
                        // 流关闭,框架校验不通过。
                        String configInfo  = configResponse.peekBody(Integer.MAX_VALUE).string();
                        Log.d("mMock", "mockUrl : " + configInfo);
                        
                        // 这一步校验是希望配置表满足某些格式,视具体情况
                        if (configInfo.contains(MockWarehouse.IDENTIFY)){
                            // 更新MockWarehouse
                            mockWarehouse.updateMockReflect(configResponse.peekBody(Integer.MAX_VALUE).string());
                        }
                        
                    } else {
                        // 拉不到配置,直接做原先的请求
                        return chain.proceed(request);
                    }
    
                }
    
                /**
                 * 尝试获取mock 的url
                 */
                String mockUrl = mockWarehouse.getMockUrl(request.url().toString());
    
                if (!TextUtils.isEmpty(mockUrl)){
                    Log.d("mMock", "mockUrl : " + mockUrl);
    
                    // 构造远端mock的请求
                    Request mockRequest = new Request.Builder()
                            .url(mockUrl)
                            .build();
    
                    //拿到远端mock请求的结果
                    Response mockResponse = chain.proceed(mockRequest);
                    // 远端mock状态
                    if (mockResponse.code() == 200){
                        String responseString = mockResponse.peekBody(Integer.MAX_VALUE).string();
                        // 校验response
                        if (!TextUtils.isEmpty(responseString)){
                            Log.d("mMock", "response : " + responseString);
    
                            // mock数据成功, 返回mock的response
                            return mockResponse;
                        }
                    }
                }
            }
    
            // 无需mock或者mock不成功,返回原来的请求
            return chain.proceed(request);
        }
    
        /**
         * 进行mock的环境条件,视具体情况而定
         * @return
         */
        private boolean isMock(){
            return true;
        }
    
    }
    

    实现中需要注意几点:

    1. isMock()视具体情况而定,比如Release包不需要进行mock
    2. 可见在此拦截器中,会根据情况的不同进行了若干次请求,对于上层来说,关心的依旧是Reponse,从何而来是透明的
    3. 需要类似MockWarehouse存储远端配置表信息并负责做URL校验
    4. MockWarehouse的配置表信息同步时机需要进行考虑,视具体情况而定是否需要考虑并发问题
    5. 可以的话,希望Reponse能保持格式化

    总结

    以上完成了Mock方案,其中思路可根据实际需要进行实现,核心为:

    • Mock也是有网络交互的过程
    • 在请求的统一入口有一层控制,视具体情况重定向
    • 远端的配置表决定API的Mock与否,本地需要同步此信息并据此进行后续操作

    相关文章

      网友评论

        本文标题:Mock你的数据,放个大招

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