解读Android官方MVP项目单元测试分析得很到位
——本篇以此为基础,有所补充本文通过分析Android官方MVP项目中最基础的todo-mvp/示例项目,来归纳如何测试。(本篇不会介绍此Demo的逻辑、源码结构,请阅读代码之后再读此文)
一、测试Presenter层
这里只说主页面的TasksPresenter中的loadTasks方法(获取所有数据)
从时序图上看,loadTask执行的逻辑是:
- 1.调用View层开启进度条
- 2.从Model层获取待办任务列表
- 3.Model层以回调函数的形式返回数据
- 4.调用View层关闭进度条
- 5.调用View层显示任务列表。
这5个步骤里,每个步骤的逻辑是否准确是View层和Model层该测试的事情,对于Presenter层来讲,他的测试任务是确保这5个步骤如期调用。为了达成此目的,我们会采用Mockito.verify()的api进行测试,这个测试类是TasksPresenterTest,代码如下:
@Test
public void loadAllTasksFromRepositoryAndLoadIntoView() {
// 塞选当前展示数据是All视图状态
mTasksPresenter.setFiltering(TasksFilterType.ALL_TASKS);
// 第0步:加载数据
mTasksPresenter.loadTasks(true);
// Callback is captured and invoked with stubbed tasks
// 第2步:验证mTasksRepository调用了getTasks,并将真实的LoadTasksCallback捕获到ArgumentCaptor<T>中
verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
// 第3步:调用捕获的回调参数,主动调用onTasksLoaded,并传入有效值
mLoadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS);
// Then progress indicator is shown
// 验证第1步:验证走了现实loading方法,进度条显示
verify(mTasksView).setLoadingIndicator(true);
// Then progress indicator is hidden and all tasks are shown in UI
// 验证第4步:验证隐藏了loading,进度条关闭
verify(mTasksView).setLoadingIndicator(false);
// 这个也是参数捕获器
ArgumentCaptor<List> showTasksArgumentCaptor = ArgumentCaptor.forClass(List.class);
// 验证第5步:View层显示待办任务列表,验证数据为之前预设的3条
verify(mTasksView).showTasks(showTasksArgumentCaptor.capture());
assertTrue(showTasksArgumentCaptor.getValue().size() == 3);
}
总结:让Presenter充当个合格的皮条客,去调用其他两层的逻辑,在假设其他两层代码逻辑都是正确的前提下,做一些mock测试,尽可能覆盖所有逻辑路径。
二、测试View层
知识补充:
1. JUnit4中的@Rule完全解析
- @Rule是JUnit4的新特性。利用@Rule我们可以扩展JUnit的功能,在执行case的时候加入测试者特有的操作,而不影响原有的case代码:减小了特有操作和case原逻辑的耦合。
- TestRule是一个工厂方法模式中的Creator角色——声明工厂方法。
- 由于工厂方法apply有参数base,因而TestRule将创建装饰模式中的装饰对象(Statement)。
AndroidJUnit4中的ActivityTestRule也是实现了TestRule,作用:
- 执行每个@Test方法之前打开相应的Activity
startIntent = new Intent(Intent.ACTION_MAIN);
startIntent.setClassName(targetPackage,mActivityClass.getName());
startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mActivityClass.cast(mInstrumentation.startActivitySync(startIntent));
- 扩展了一些方法,在Activity创建之前以及之后的相应方法
beforeActivityLaunched();
afterActivityLaunched();
- 确保模拟的操作均在UI线程中执行
getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
try {
mBase.evaluate();
} catch (Throwable throwable) {
exceptionRef.set(throwable);
}
}
});
2.Espresso的测试等待异步结果机制
开发者面临的挑战之一,是在编写UI测试时需要等待异步计算或I/O操作完成。这里使用Espresso测试框架来解决这个问题和学到的一些技巧。
Idling Resource
Espresso引入了Idling Resource的概念,它是一个简单的接口:
它代表了被测应用程序的资源,这个资源在测试执行时可以在后台异步工作。
接口定义了三个方法:
- getName():必须返回代表idling resource的非空字符串;
- isIdleNow():返回当前idlingresource的idle状态。如果返回true,onTransitionToIdle()上注册的ResourceCallback必须必须在之前已经调用;
- registerIdleTransitionCallback(IdlingResource.ResourceCallback callback):通常此方法用于存储对回调的引用来通知idle状态的变化。
- 创建完自定义的idling resource后,需要通过调用Espresso.registerIdlingResource(webViewIdlingResource)与Espresso注册。
总结:Idling Resource其实是Espresso的一个用来等待异步耗时操作的一个指示器(是否是空闲状态)、通知器(转为空闲状态时,通知Espresso)
- 先通过Espresso.registerIdlingResource在测试中将Idling Resource注册给Espresso
- 异步耗时操作之前调节自定义的Idling Resource中的参数,使得现在isIdleNow()返回false(即标记为非空闲状态)
- 异步耗时操作结束时,修改自定义的Idling Resource中的参数,使得现在isIdleNow()返回true(即标记为空闲状态)
- 再调用registerIdleTransitionCallback(ResourceCallback resourceCallback),Espresso回调的ResourceCallback参数的onTransitionToIdle()方法,告知Espresso状态转到空闲
开启View层的测试
其实View层的测试是从QA角度出发,以测试用例为基本单位,做得好甚至可以用Suite结合起来,做到自动化测试,Google下的绝大多数App其实都运用了这个测试框架进行UI测试。
- 第一步:测试哪个页面
@Rule
public ActivityTestRule<TasksActivity> mTasksActivityTestRule =
new ActivityTestRule<TasksActivity>(TasksActivity.class);
- 第二步:给Espresso注册Idling Resource
@Before
public void registerIdlingResource() {
Espresso.registerIdlingResources(
mTasksActivityTestRule.getActivity().getCountingIdlingResource());
}
实际代码中自定义的Idling Resource需要在特定地方进行标记
- 第三部开始测试
站在QA的角度,我们想要验证待办任务列表时候,会设计以下的测试用例:
请点击此入口进入这部分内容
总结:这一层的测试站在用户的角度,进行黑盒测试,在功能层面测试输入(用户操作)输出(用户得到的界面反馈),其实已经粗糙的覆盖了M和P的逻辑了,做好其实就是自动化测试了。
三、Model层的测试
这部分与UI无关,有部分Model层的逻辑,网络、数据库、本地文件等部分需要Android环境,所以:
- Model纯逻辑部分:Local Test > JUnit4 + Mockito + Hamcrest > 存放test下
- Model需要Android环境部分:AndroidJUnit4 > 存放 androidTest 下
这张图不得不盗用下
Model层注意(Fake、门面模式):
- 网络请求:不测试真实的网络请求,但提供了Fake供其他层调用测试,其实是面相接口的空实现。
- 封装的门面类:决定了数据的来源和去向是来自于本地数据库 or 网络 or 内存,此为真正对其他层暴露的Model类。此类不做数据准确性的验证,只做mock测试,验证覆盖路径。UT选型Junit+Mockito,代码存放于test中。
MVP模式少了Dragger2总不那么完美,下一篇结合Dragger2的单元测试会有点久