Android Espresso单元测试
Posted xuguoli_beyondboy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Espresso单元测试相关的知识,希望对你有一定的参考价值。
Espresso介绍
在android单元测试中,谷歌官方推荐使用Espresso框架,根据谷歌官方介绍,Espresso的最关键的优势就是它能自动同步模拟行为对UI的测试,它能够检测到主线程空闲状态的时候,以便在适当的时候运行你的测试代码或命令,这样你就没必要通过sleep去让主线程睡眠的方式去同步测试。说白了就是Espresso框架测试app不会通过阻塞主线程去同步UI测试。
Espresso有三种重要体系的类,分别是Matchers(匹配器),ViewAction(界面行为),ViewAssertions(界面判断),其中Matchers是常常是通过匹配条件来需找UI组件或过滤UI,而ViewAction是来模拟用户操作界面的行为,ViewAssertions对模拟行为操作的View进行变换和结果验证,其三者关系如图所示:
具体Espresso使用文档和Android 官方使用可以参考下面官方资料:
https://google.github.io/android-testing-support-library/docs/index.html
http://developer.android.com/training/testing/ui-testing/espresso-testing.html
Espresso实战
模拟用户行为测试:
代码:
package com.scau.beyondboy.idgoods;
import android.support.test.espresso.contrib.PickerActions;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import android.widget.DatePicker;
import org.hamcrest.Matchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.clearText;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
/**
* Author:beyondboy
* Gmail:xuguoli.scau@gmail.com
* Date: 2015-10-25
* Time: 16:21
*/
//设置测试运行环境
@RunWith(AndroidJUnit4.class)
public class PersonInforTest
private static final String TAG = PersonInforTest.class.getName();
//设置启动的Activity
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule=new ActivityTestRule<MainActivity>(MainActivity.class);
//模拟用户的点击行为
private void clicktest(final int id)
onView(withId(id)).perform(click());
//改变View的文本显示
private String changetexttest(final int id,String text)
onView(withId(id)).perform(clearText(),typeText(text),closeSoftKeyboard());
return text;
//检查View文本变化是否正确
private void checktexttest(final int id,String text)
onView(withId(id)).check(matches(withText(text)));
//测试方法
@Test
public void NickNameTest()
clicktest(R.id.menu_toggle);
clicktest(R.id.header_image);
clicktest(R.id.nickname_layout);
final String text=changetexttest(R.id.nickname,"test");
clicktest(R.id.save);
checktexttest(R.id.nickname, text);
运行结果:
测试成功会显示绿条:
测试失败会显示红褐色条并报出对应的错误信息:
Espresso异步测试:
在开发中,我们经常会碰到异步请求,等异步请求完成后,再去更新UI,那么测试如何进行,估计很多测试新手可能会想到用Thread.sleep()方法让主线程睡眠等待,直到异步请求完成,刚开始我也是这么想的,但仔细想想这种方法是不对啊,我们知道主线程阻塞不能超过五秒,已超过五秒就会引起ANR异常,这种方式明显是不可行,那么有没有更好的方法呢?有,那就是通过Espresso的IdlingResource类来去完成,那是专门处理测试中的异步操作的发生,里面有两个相当重要的方法,其解释如下:
public void registerIdleTransitionCallback(ResourceCallback callback);
这个方法注册回调。
其回调接口:
public interface ResourceCallback
/**
* Called when the resource goes from busy to idle.
*/
public void onTransitionToIdle();
public boolean isIdleNow();
此方法通常用来通知主线程,其异步操作的完成,好让主线程更新UI,返回true便通知主线程去更新UI线程。
代码:
package com.scau.beyondboy.idgoods;
import android.support.test.espresso.IdlingResource;
import com.scau.beyondboy.idgoods.view.SlideListView;
/**
* Author:beyondboy
* Gmail:xuguoli.scau@gmail.com
* Date: 2015-12-16
* Time: 13:34
* 实现ListView异步空闲处理类
*/
public class ListAdapterIdlingResource implements IdlingResource
private SlideListView mSlideListView;
private IdlingResource.ResourceCallback mCallback;
private final long startTime;
private final long waitingTime;
public ListAdapterIdlingResource(long waitingTime,SlideListView slideListView)
this.startTime = System.currentTimeMillis();
this.waitingTime = waitingTime;
this.mSlideListView=slideListView;
@Override
public String getName()
return "listadapterIdlingResource";
@Override
public boolean isIdleNow()
//当网络数据加载完,才设置适配器,故可以通过适配器是否为空值来判断其异步数据加载是否完成
if(mSlideListView.getAdapter()!=null)
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;
package com.scau.beyondboy.idgoods;
import android.support.test.espresso.Espresso;
import android.support.test.espresso.IdlingPolicies;
import android.support.test.espresso.IdlingResource;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import com.scau.beyondboy.idgoods.view.SlideListView;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.clearText;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
/**
* Author:beyondboy
* Gmail:xuguoli.scau@gmail.com
* Date: 2015-12-14
* Time: 20:16
*/
@RunWith(AndroidJUnit4.class)
public class ProductTest
private static final String TAG = ProductTest.class.getName();
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule=new ActivityTestRule<MainActivity>(MainActivity.class);
private void clicktest(final int id)
onView(withId(id)).perform(click());
private String changetexttest(final int id, String text)
onView(withId(id)).perform(clearText(),typeText(text),closeSoftKeyboard());
return text;
private void checktexttest(final int id,String text)
onView(withId(id)).check(matches(withText(text)));
@Test
public void testClickOnItem()
clicktest(R.id.menu_toggle);
clicktest(R.id.myproduct);
int waitingTime=1000;
//设置异步操作测试超时时间
IdlingPolicies.setMasterPolicyTimeout(
waitingTime * 10 TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(
waitingTime * 10, TimeUnit.MILLISECONDS);
IdlingResource idlingResource=new ListAdapterIdlingResource(1000,(SlideListView)mActivityTestRule.getActivity().findViewById(R.id.product_slidelistview));
//等待后台ListView加载完数据后执行后面的代码
Espresso.registerIdlingResources(idlingResource);
//选中一个listView的item选项 onData(anything()).inAdapterView(withId(R.id.product_slidelistview)).atPosition(10).perform(click());
//释放对其异步空闲处理类
Espresso.unregisterIdlingResources(idlingResource);
clicktest(R.id.header_image);
checktexttest(R.id.product_name, "其他的商品");
pressBack();
pressBack();
运行结果如图:
Espresso Intent测试:
代码:
package com.scau.beyondboy.idgoods;
import android.support.test.espresso.intent.Intents;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
/**
* Author:beyondboy
* Gmail:xuguoli.scau@gmail.com
* Date: 2016-01-07
* Time: 12:57
* 第一种Intent测试
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class IntentTest
@Rule
public final ActivityTestRule<MainActivity> rule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void intentTest()
//这种必须调用Intetns.init()方法
Intents.init();
clicktest(R.id.menu_toggle);
clicktest(R.id.header_image);
intended(hasComponent(PersonInfoActivity.class.getName()));
clicktest(R.id.email_layout);
intended(hasComponent(ChangeEmailActivity.class.getName()));
Intents.release();
private void clicktest(final int id)
onView(withId(id)).perform(click());
package com.scau.beyondboy.idgoods;
import android.support.test.espresso.intent.Intents;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
/**
* Author:beyondboy
* Gmail:xuguoli.scau@gmail.com
* Date: 2016-01-07
* Time: 12:57
* 第二种Intent测试
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class IntentTest
@Rule
public IntentsTestRule<MainActivity> mRule = new IntentsTestRule<>(MainActivity.class);
@Test
public void intentTest()
clicktest(R.id.menu_toggle);
clicktest(R.id.header_image);
intended(hasComponent(PersonInfoActivity.class.getName()));
clicktest(R.id.email_layout);
intended(hasComponent(ChangeEmailActivity.class.getName()));
private void clicktest(final int id)
onView(withId(id)).perform(click());
运行结果:
参考资料:
http://michaelevans.org/blog/2015/09/15/testing-intents-with-espresso-intents/
https://github.com/JakeWharton/double-espresso/tree/gradle/espresso-sample/src/androidTest/java/com/google/android/apps/common/testing/ui/testapp
以上是关于Android Espresso单元测试的主要内容,如果未能解决你的问题,请参考以下文章
使用Espresso Test Recorder编写Android测试
在ANDROID STUDIO环境下使用JUNIT框架进行单元测试
Android - Espresso:为每次测试重新创建活动
Android 自动化测试 Espresso篇:异步代码测试
Android Studio 2.2 Espresso Test Recorder-----解放双手,通过录制测试过程实现测试