Espresso 的核心是它可以与待测应用无缝同步测试操作的能力。默认情况下,Espresso 会等待当前消息队列中的 UI 事件执行(默认是 AsyncTask)完毕再进行下一个测试操作。这应该能解决大部分应用与测试同步的问题。
然而,应用中有一些执行后台操作的对象(比如与网络服务交互)通过非标准方式实现;例如:直接创建和管理线程,以及使用自定义服务。
此种情况,我们建议你首先提出可测试性的概念,然后询问使用非标准后台操作是否必要。某些情况下,可能是由于对 Android 理解太少造成的,并且应用也会受益于重构(例如,将自定义创建的线程改为 AsyncTask)。然而,某些时候重构并不现实。庆幸的是 Espresso 仍然可以同步测试操作与你的自定义资源。
这是基于idling的概念:Espresso等待app处于idle状态,才会执行下个动作和检查下个断言.
那么app处于idle状态是什么意思?Espresso检查下面几个场景:
- 在当前消息队列中没有UI事件;
- 在默认的AsyncTask线程池没有任务;
但是,如果app以其他方式执行长时间运行操作,Espresso不知道如何判断这些操作已经完成。如果是这样的话,可以通过编写自定义的IdelingResource来通知Espresso的等待时间。
idlingResource
它是一个简单的接口:
它代表了被测应用程序的资源,这个资源在测试执行时可以在后台异步工作。
接口定义了三个方法:
getName():必须返回代表idling resource的非空字符串;
isIdleNow():返回当前idlingresource的idle状态
如果返回true,onTransitionToIdle()上注册的ResourceCallback必须必须在之前已经调用;registerIdleTransitionCallback:通常此方法用于存储对回调的引用来通知idle状态的变化。
官方示例
需要入侵源代码,不做过多描述
自定义 示例1
问题:
点击界面某个按钮后,新出现的页面会存在一个progressDialogFragment进行网络的请求数据,直到网络异常后者请求成功才会消失
其实这个时候如何不做任何处理的话,很明显,Espresso在点击按钮后的其他操作都会失败,它肯定会报错说NoViewsMatching
所以我们需要自定义一个IdlingResource:
public class ProgressFramentIdlingResource implements IdlingResource {
private FragmentManager fragmentManager;
private ResourceCallback resourceCallback;
public ProgressFramentIdlingResource(FragmentManager fg) {
fragmentManager = fg;
}
@Override
public String getName() {
return ProgressFramentIdlingResource.class.getName();
}
@Override
public boolean isIdleNow() {
//通过当前页面的fragment 是否存在以及可见,来判断。
for (Fragment fragment:fragmentManager.getFragments()) {
if(fragment != null && fragment.isVisible()) {
resourceCallback.onTransitionToIdle();
return false;
}
}
return true;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
resourceCallback = callback;
}
}
上面的方法是没问题的,但是问题来了,如何才能够获取到framgentManager呢。正常情况下我们只是需要activity.getSupportFragmentManager就可以解决,但是我们在Espresso中获取到的activity的对象却是上一个界面的对象,所以我们还得去有方法能够获取到当前的Acitivity。还是伟大的google帮忙了
public Activity getActivityInstance(){
try {
testRule.runOnUiThread(new Runnable() {
public void run() {
Collection<Activity> resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
for (Activity act: resumedActivities){
Log.d("Your current activity: ", act.getClass().getName());
currentActivity = act;
break;
}
}
});
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return currentActivity;
}
上面的方法就解决了获取当前Activity的问题了,具体的使用
IdlingResource idlingResource = new ProgressFramentIdlingResource(((UpdateSubjectActivity)getActivityInstance()).getSupportFragmentManager());
Espresso.registerIdlingResources(idlingResource);
****
Espresso.unregisterIdlingResources(idlingResource);
自定义 示例2
假设你使用IntentService来做一些长时间运算,然后通过broadcast将结果返回给activity。我们希望Espresso一直等到结果返回,才来验证界面显示正确。
为了实现IdlingResource,需要重写3个函数:getName(),registerIdleTransitionCallback(),isIdleNow()
@Override
public String getName() {
return IntentServiceIdlingResource.class.getName();
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
@Override
public boolean isIdleNow() {
boolean idle = !isIntentServiceRunning();
if (idle && resourceCallback != null) {
resourceCallback.onTransitionToIdle();
}
return idle;
}
private boolean isIntentServiceRunning() {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo info : manager.getRunningServices(Integer.MAX_VALUE)) {
if (RepeatService.class.getName().equals(info.service.getClassName())) {
return true;
}
}
return false;
}
idle逻辑是在isIdleNow()实现的。在这个例子中,我们通过查询ActivityManager来检查IntentService是否正在运行。如果IntentService停止运行,我们调用resourceCallback.onTransitionToIdle()来通知Espresso
为了让Espresso等待自定义的idling resource,你需要注册它。在测试代码的@Before方法中执行注册,在@After中执行注销
@Before
public void registerIntentServiceIdlingResource() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
idlingResource = new IntentServiceIdlingResource(instrumentation.getTargetContext());
Espresso.registerIdlingResources(idlingResource);
}
@After
public void unregisterIntentServiceIdlingResource() {
Espresso.unregisterIdlingResources(idlingResource);
}
自定义 示例3
public class MainActivityIdlingResource implements IdlingResource {
private ResourceCallback mCallback;
private final long startTime;
private final long waitingTime;
ActivityTestRule testRule;
public MainActivityIdlingResource(long waitingTime, ActivityTestRule testRule)
{
this.startTime = System.currentTimeMillis();
this.waitingTime = waitingTime;
this.testRule = testRule;
}
@Override
public String getName() {
return MainActivityIdlingResource.class.getSimpleName();
}
@Override
public boolean isIdleNow() {
//当Activity 出现时
if(ActivityUtils.getActivityInstance(testRule).getClass().getSimpleName().equals("MoaFragmentTabActivity"))
{
mCallback.onTransitionToIdle();
System.out.println("打印");
return true;
}
return false;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.mCallback=callback;
}
}
IdlingResource idlingResource1=new MainActivityIdlingResource(1000, mActivityTestRule);
Espresso.registerIdlingResources(idlingResource1);//等待主界面后执行后面的代码
...
Espresso.unregisterIdlingResources(idlingResource1); //等代码调试完记得关闭
自定义 示例4
public class ListAdapterIdlingResource implements IdlingResource {
private IdlingResource.ResourceCallback mCallback;
private final long startTime;
private final long waitingTime;
private ListView listView;
public ListAdapterIdlingResource(long waitingTime,ListView listView)
{
this.startTime = System.currentTimeMillis();
this.waitingTime = waitingTime;
this.listView=listView;
}
@Override
public String getName() {
return ListAdapterIdlingResource.class.getSimpleName();
}
@Override
public boolean isIdleNow() {
//当网络数据加载完,才设置适配器,故可以通过适配器是否为空值来判断其异步数据加载是否完成
if(listView.getAdapter().getCount() != 0)
{
mCallback.onTransitionToIdle();
System.out.println("打印");
return true;
}
return false;
//通过时间来限制其异步加载
/*long elapsed = System.currentTimeMillis() - startTime;
boolean idle = (elapsed >= waitingTime);
if (idle) {
System.out.println("打印");
mCallback.onTransitionToIdle();
}
return idle;*/
}
@Override
public void registerIdleTransitionCallback(ResourceCallback callback) {
this.mCallback=callback;
}
}
IdlingResource idlingResource = new ListAdapterIdlingResource(1000, (((PullListView) ActivityUtils.getActivityInstance(mActivityTestRule).findViewById(R.id.pull)).getRefreshableView()));
//等待后台ListView加载完数据后执行后面的代码
...
//释放对其异步空闲处理类
Espresso.unregisterIdlingResources(idlingResource);