1. 是什么?
是可以帮你做异步测试的支持包,简单粗暴的可以理解为不需要再写傻傻的Thread.sleep(毫秒)了.
2. 怎么做?
取决于你的业务场景.
注意:后续描述仅以 Native 场景为例—检查点之前和检查点之后操作的控件对象不包含 webview
基本上可以粗略的分为两大场景:1) 有页面跳转的 - 跳转之后,之前页面的页面控件就找不到了,出现了新的页面及其控件集; 2) 没有页面跳转的 - 页面控件没有变化,只是其某些属性发生了变化 (比如 text 属性值)
示例业务逻辑:点击主页面的 tv_main_username,会有一个弹框 (与主页面的布局可视性互斥),输入正确的帐密后,点击登录按钮,弹框消失,主页面重新渲染 (之前可能 tv_main_username 是匿名,现在变成一个存在的账户名)
这个稍微复杂一点,但是懂了之后也很简单:在测试主线程的任何地方传一个会消失的控件 (这个控件可以通过当前的 Activity 的某个方法或者属性暴露出来, 注意:弹框的方式可以有很多种实现方式,要看开发怎么实现的,通常的方式有 3 种:单独的 activity,一个 fragment 或者 一个 Dialog。要根据具体情况做具体调用!) 或者把控件的状态传给idlingResource,示例代码如下 (笔者项目的 APP 是以 Dialog 方式实现的):
MainActivity.java
......
//为了方便自动化测试,将此Dialog暴露出来
public LoginDialog getLoginDialog() {
return loginDialog;
}
private LoginDialog loginDialog;
......
MainIdlingResource.java可以放在项目的任何地方,只要你能调的到 (最好是放在测试支撑包 - 比如取个名字叫 TestHelper 下面,便于统一管理)
package com.*.*;
import android.support.test.espresso.IdlingResource;
//如果是Fragment实现方式就用下面两个包
//import android.support.v4.app.Fragment;
//import android.support.v4.app.FragmentManager;
import *.*.*.packageupdate.filedownloader.base.Log;
public class MainIdlingResource implements IdlingResource {
// private final boolean idle;
private final MainActivity mActivity;
private ResourceCallback resourceCallback;
MainIdlingResource(MainActivity mActivity){
// this.idle = idle;
this.mActivity = mActivity;
}
@Override
public String getName() {
return "MainIdlingResource";
}
@Override
public boolean isIdleNow() {
if (resourceCallback != null) {
// boolean idle = (manager.findFragmentByTag(tag) == null);
boolean idle = mActivity.getLoginDialog().isShowing();
//如果木有在显示了,那么说明登录框已经消失了,登录完毕了
if (!idle) {
Log.i("DDD",idle+"可以检查了");
resourceCallback.onTransitionToIdle();
return true;
}
else{
Log.i("DDD",idle+"还不能检查");
return false;
}
}
else
{
Log.i("AAA","EEE");
return true;
}
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
测试代码
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@RunWith(AndroidJUnit4.class)
public class Login{
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class);
public void loginSuccess(){
//看弹框的实现方式,如果是Fragment类型的,就用下面的 - 传一个FragmentManager进去,用它判断其承载的子控件是否存在
//IdlingResource idlingResource = new MainIdlingResource(mActivityRule.getActivity().getSupportFragmentManager());
//如果是Dialog,实现方式,则直接通过Activity找Dialog然后一定要在MainIdlingResource里判断它是否showing来回调是否放开测试主进程
onView(withId(R.id.tv_main_username)).perform(click());
onView(withId(R.id.et_username)).perform(clearText());
//把Activity实例传过去,然后在IdlingResource里面找出dialog并判断是否还在显示
IdlingResource idlingResource = new MainIdlingResource(mActivityRule.getActivity());
onView(withId(R.id.et_username)).perform(typeText("............"));
onView(withId(R.id.et_password)).perform(clearText());
onView(withId(R.id.et_password)).perform(typeText("........."));
onView(withId(R.id.bt_user_login)).perform(click());
IdlingRegistry.getInstance().register(idlingResource);
onView(withId(R.id.tv_main_username)).check(matches(withText("Hello")));
//检查完毕之后,立马注销idlingResource
IdlingRegistry.getInstance().unregister(idlingResource);
}
}
示例业务逻辑:页面有一个 textview 控件,在一段时间后,会从空文本 (text) 属性更新为 “测试异步操作”。
这个就简单很多,示例代码:
MainIdlingResource.java
package com.yu.espressotest;
import android.support.test.espresso.IdlingResource;
import android.text.TextUtils;
import android.widget.TextView;
/**
* Created by yu on 2017/6/28.
* 异步通知
*/
public class MainIdlingResource implements IdlingResource{
private ResourceCallback mCallback;
private TextView tvResult;
// 构造方法传入 tvResult 文本控件,用来判断异步是否完成
public MainIdlingResource(TextView textView){
this.tvResult = textView;
}
// 返回一个名字 一般用于日志打印
@Override
public String getName() {
return "MainIdlingResource";
}
//通知 Espresso 异步操作是否完成
@Override
public boolean isIdleNow() {
if (mCallback != null) {
//当 tvResult 文字不为空的时候表示异步完成
if (!TextUtils.isEmpty(tvResult.getText())) {
// 这里一定要通知 Espresso 切换为空闲状态
mCallback.onTransitionToIdle();
return true;
}else {
return false;
}
}else {
return true;
}
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
mCallback = callback;
}
}
@RunWith(JUnit4.class)
@LargeTest
public class MainActivityTest {
@Rule
public ActivityTestRule<MainActivity> mActivity = new ActivityTestRule<>(MainActivity.class);
//异步测试
@Test
public void idlingTest() throws InterruptedException {
//不做异步处理 会报错 因为此时 TextView 中的文字还为空串
//Thread.sleep(6000);//这很危险 可能会导致 ANR
//onView(withId(R.id.tv_result)).check(matches(withText("测试异步操作")));
//使用 IdlingResource 做异步通知
IdlingResource idlingResource = new MainIdlingResource((TextView) mActivity.getActivity().findViewById(R.id.tv_result));
//注册 IdlingResources , 之后的 UI 操作会被阻塞(非 UI 操作之前的代码,好像仍不会阻塞)
Espresso.registerIdlingResources(idlingResource);
//异步完成检查结果是否正确
onView(withId(R.id.tv_result)).check(matches(withText("测试异步操作")));
//取消 IdlingResource 注册
Espresso.unregisterIdlingResources(idlingResource);
}
Look! 我们没有花任何多余的时间在无意义的等待上,整个测试过程,可以说把效率发挥到了极致!
有疑问请留言,知无不言.
下一步就是 Hybird 类型的互相跳转问题了.待续~
参考文献:
chiuki/espresso-samples
Espresso 入门 及 异步测试