模拟服务器数据接口 - MockApi
为了方便app开发过程中不受服务器接口的限制便于客户端功能的快速测试可以在客户端实现一个模拟服务器数据接口的MockApi模块。本篇文章就尝试为使用gradle的android项目设计实现MockApi。需求概述在app开发过程中在和服务器人员协作时一般会第一时间确定数据接口的请求参数和返回数据格式然后服务器人员会尽快提供给客户端可调试的假数据接口。不过有时候就算是假数据接口也来不及提供或者是接口数据格式来回变动——很可能是客户端展示的原因这个是产品设计决定的总之带来的问题就算服务器端的开发进度会影响客户端。所以如果可以在客户端的正常项目代码中自然地不影响最终apk添加一种模拟服务器数据返回的功能这样就可以很方便的在不依赖服务器的情况下展开客户端的开发。而且考虑一种情况为了测试不同网络速度网络异常以及服务器错误等各种“可能的真实数据请求的场景”对客户端UI交互的影响我们往往需要做很多手动测试——千篇一律如果本地有一种控制这种服务器响应行为的能力那真是太好了。本文将介绍一种为客户端项目增加模拟数据接口功能的方式希望能减少一些开发中的烦恼。设计过程下面从分层设计、可开关模拟模块、不同网络请求结果的制造这几个方面来阐述下模拟接口模块的设计。为了表达方便这里要实现的功能表示为“数据接口模拟模块”对应英文为MockDataApi或简写为MockApi正常的数据接口模块定义为DataApi。分层思想说到分层设计MVC、MVP等模式一定程度上就起到了对代码所属功能的一个划分。分层设计简单的目标就是让项目代码更加清晰各层相互独立好处不多说。移动app的逻辑主要就是交互逻辑然后需要和服务器沟通数据。所以最简单的情形下可以将一个功能比如一个列表界面的实现分UI层和数据访问层。下面将数据访问层表述为DataApi模块DataApi层会定义一系列的接口来描述不同类别的数据访问请求。UI层使用这些接口来获取数据而具体的数据访问实现类就可以在不修改UI层代码的情况下进行替换。例如有一个ITaskApi定义了方法ListTask getTasks()UI层一个界面展示任务列表那么它使用ITaskApi来获取数据而具体ITaskApi的实现类可以由DataApi层的一个工厂类DataApiManager来统一提供。有了上面的分层设计就可以为UI层动态提供真实数据接口或模拟数据接口。模拟接口的开关可能大家都经历过在UI层代码里临时写一些假数据得情况。比如任务列表界面开发初可以写一个mockTaskData()方法来返回一个ListTask。但这种代码只能是开发阶段有最终apk不应该存在。不能让“模拟数据”的代码到处散乱在分层设计的方式下可以将真实的数据接口DataApi和模拟数据接口MockDataApi分别作为两个数据接口的实现模块这样就可以根据项目的构建类型来动态提供不同的数据接口实现。实现MockDataApi的动态提供的方法也不止一种。一般的java项目可以使用“工厂模式反射”来动态提供不同的接口实现类再专业点就是依赖注入——DI框架的使用了。目前gradle是java的最先进的构建工具它支持根据buildType来分别指定不同的代码资源或不同的依赖。可以在一个单独的类库module就是maven中的项目中来编写各种MockDataApi的实现类然后主app module在debug构建时添加对它的依赖此时数据接口的提供者DataApiManager可以向UI层返回这些mock类型的实例。为了让“正常逻辑代码”和mock相关代码的关联尽量少可以提供一个MockApiManager来唯一获取各个MockDataApi的实例。然后在debug构建下的MockApiManager会返回提供了mock实现的数据接口实例而release构建时MockApiManager会一律返null。不同请求结果的模拟MockApi在多次请求时提供不同的网络请求结果如服务器错误网络错误成功等并模拟出一定的网络延迟这样就很好的满足了UI层代码的各种测试需求。为了达到上述目标定义一个接口IMockApiStrategy来表示对数据请求的响应策略它定义了方法onResponse(int callCount)。根据当前请求的次数callCountonResponse()会得到不同的模拟响应结果。很明显可以根据测试需要提供不同的请求响应策略比如不断返回成功请求或者不断返回错误请求或轮流返回成功和错误等。关键代码解析下面就给出各个部分的关键代码来说明以上所描述的MockDataApi模块的实现。UI层代码作为示例界面MainActivity是一个“任务列表”的展示。任务由Task类表示public class Task { public String name; }界面MainActivity使用一个TextView来显示“加载中、任务列表、网络错误”等效果并提供一个Button来点击刷新数据。代码如下public class MainActivity extends Activity { private TextView tv_data; private boolean requesting false; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_data (TextView) findViewById(R.id.tv_data); getData(); } private void getData() { if (requesting) return; requesting true; ITaskApi api DataApiManager.ofTask(); if (api ! null) { api.getTasks(new DataApiCallbackListTask() { Override public void onSuccess(ListTask data) { // 显示数据 StringBuilder sb new StringBuilder(请求数据成功\n); for (int i 0; i data.size(); i) { sb.append(data.get(i).name).append(\n); } tv_data.setText(sb.toString()); requesting false; } Override public void onError(Throwable e) { // 显示错误 tv_data.setText(错误\n e.getMessage()); requesting false; } Override public void onStart() { // 显示loading tv_data.setText(正在加载...); } }); } } public void onRefreshClick(View view) { getData(); } }在UI层代码中使用DataApiManager.ofTask()获得数据访问接口的实例。考虑到数据请求会是耗时的异步操作这里每个数据接口方法接收一个DataApiCallbackT回调对象T是将返回的数据类型。public interface DataApiCallbackT { void onSuccess(T data); void onError(Throwable e); void onStart(); }接口DataApiCallback定义了数据接口请求数据开始和结束时的通知。DataApiManager根据分层设计UI层和数据访问层之间的通信就是基于DataApi接口的每个DataApi接口提供一组相关数据的获取方法。获取Task数据的接口就是ITaskApipublic interface ITaskApi { void getTasks(DataApiCallbackListTask callback); }UI层通过DataApiManager来获得各个DataApi接口的实例。也就是在这里会根据当前项目构建是debug还是release来选择性提供MockApi或最终的DataApi。public class DataApiManager { private static final boolean MOCK_ENABLE BuildConfig.DEBUG; public static ITaskApi ofTask() { if (MOCK_ENABLE) { ITaskApi api MockApiManager.getMockApi(ITaskApi.class); if (api ! null) return api; } return new NetTaskApi(); } }当MOCK_ENABLE为true时会去MockApiManager检索一个所需接口的mock实例如果没找到会返回真实的数据接口的实现上面的NetTaskApi就是。倘若现在服务器还无法进行联合调试它的实现就简单的返回一个服务器错误public class NetTaskApi implements ITaskApi { Override public void getTasks(DataApiCallbackListTask callback) { // 暂时没用实际的数据接口实现 callback.onError(new Exception(数据接口未实现)); } }MockApiManagerDataApiManager利用MockApiManager来获取数据接口的mock实例。这样的好处是模拟数据接口的相关类型都被“封闭”起来仅通过一个唯一类型来获取已知的DataApi的一种这里就指mock实例。这样为分离出mock相关代码打下了基础。在DataApiManager中获取数据接口实例时会根据开关变量MOCK_ENABLE判断是否可以返回mock实例。仅从功能上看是满足动态提供MockApi的要求了。不过为了让最终release构建的apk中不包含多余的mock相关的代码可以利用gradle提供的buildVariant。buildVariant使用gradle来构建项目时可以指定不同的buildType默认会有debug和release两个“构建类型”。此外还可以提供productFlavors来提供不同的“产品类型”如demo版专业版等。每一种productFlavor和一个buildType组成一个buildVariant构建变种。可以为每一个buildTypebuildVariant或productFlavor指定特定的代码资源。这里利用buildType来为debug和release构建分别指定不同的MockApiManager类的实现。默认的项目代码是在src/main/java/目录下创建目录/src/debug/java/来放置只在debug构建时编译的代码。在/src/release/java/目录下放置只在release构建时编译的代码。debug构建时的MockApiManagerpublic class MockApiManager { private static final MockApiManager INSTANCE new MockApiManager(); private HashMapString, BaseMockApi mockApis; private MockApiManager() {} public static T T getMockApi(ClassT dataApiClass) { if (dataApiClass null) return null; String key dataApiClass.getName(); try { T mock (T) getInstance().mockApis.get(key); return mock; } catch (Exception e) { return null; } } private void initApiTable() { mockApis new HashMap(); mockApis.put(ITaskApi.class.getName(), new MockTaskApi()); } private static MockApiManager getInstance() { if (INSTANCE.mockApis null) { synchronized (MockApiManager.class) { if (INSTANCE.mockApis null) { INSTANCE.initApiTable(); } } } return INSTANCE; } }静态方法getMockApi()根据传递的接口类型信息从mockApis中获取可能的mock实例mockApis中注册了需要mock的那些接口的实现类对象。release构建时的MockApiManagerpublic class MockApiManager { public static T T getMockApi(ClassT dataApiClass) { return null; } }

相关新闻