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
//设置测试运行环境
@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)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
运行结果:
测试成功会显示绿条:
测试失败会显示红褐色条并报出对应的错误信息:
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
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
//通过时间来限制其异步加载
}
@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
@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()
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
运行结果如图:
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
@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
@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())
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
运行结果:
参考资料:
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