Android

Mock你的数据,放个大招

2019-02-09  本文已影响503人  MxsQ

前言

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

为何要Mock

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

常见解决方案

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

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

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

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

放个大招

理想状态下,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数据和配置表,如果没有后端条件或者没有合适方案,可以使用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方案,其中思路可根据实际需要进行实现,核心为:

上一篇 下一篇

猜你喜欢

热点阅读