Android测试框架:Espresso
Espresso 基础
依赖的包:
espresso-core: 包括核心和基础的view匹配器,动作,断言。
espresso-web: 包括支持webview的资源。
espresso-idling-resource: espresso同步后台工作的机制。
espresso-contrib: 外部支持,包括日期选择器,RecyclerView和绘制动作,断言检查,CountingIdlingResource。
espresso-intents: hermetic测试的有效扩展名和stub意图。
添加依赖:
app/build.gradle
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'
android.defaultConfig下添加工具运行器
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
Espresso主要组成部分:
ViewMatchers: ViewInteraction v = onView(ViewMatchers.withId(R.id.x))
ViewActions: v.perform(ViewActions.click())
click() | click on the view |
typeText() | click on a view and enters a specified string |
scrollTo() | scroll to the view, the target view must be subclassed from ScrollView and its property should be VISIBLE |
pressKey() | perform a key press using a specified keycode |
clearText() | clear the text in the target view |
ViewAssertions: v.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
AdapterView中使用onData(ListView, GridView...)
AdapterView 是一种特殊的类型,它从Adapter中动态地加载数据。AdapterView最通常的例子就是ListView。与LinerLayout静态widgets相反,只有一部分AdapterView子view会被加载到当前的view层级中,通过onView进行查询时当前未加载的view将不会被找到。Espresso通过onData()预先操作它或它的子类来处理即将加载的adapter的item。
初始化时就显示在屏幕上的adapter中的view你也可以不使用onData()因为它们已经被加载了。然而,使用onData()会更加安全。
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
异步代码测试
我们想要对异步代码进行单元测试,首先要了解IdlingResource这个接口,接口需要我们实现的方式如下:
public interface IdlingResource { /** * 用来标识 IdlingResource 名称 */ public String getName(); /** * 当前 IdlingResource 是否空闲 . */ public boolean isIdleNow(); /** * 注册一个空闲状态变换的ResourceCallback回调 */ public void registerIdleTransitionCallback(ResourceCallback callback); /** * 通知Espresso当前IdlingResource状态变换为空闲的回调接口 */ public interface ResourceCallback { /** * 当前状态转变为空闲时,调用该方法告诉Espresso */ public void onTransitionToIdle(); } }
接下来看一下如何实现这个接口:
1 public final class SimpleCountingIdlingResource implements IdlingResource { 2 3 private final String resourceName; 4 5 private final AtomicInteger counter = new AtomicInteger(0); 6 7 private volatile ResourceCallback resourceCallback; 8 9 public SimpleCountingIdlingResource(String resourceName){ 10 11 this.resourceName = resourceName 12 } 13 14 15 @Override 16 public String getName() { 17 return this.resourceName; 18 } 19 20 @Override 21 public boolean isIdleNow() { 22 return counter.get() ==0; 23 } 24 25 @Override 26 public void registerIdleTransitionCallback(ResourceCallback resourceCallback){ 27 this.resourceCallback = resourceCallback; 28 } 29 30 //每当我们开始异步请求, 把counter 值+1 31 public void increment() { 32 counter.getAndIncrement(); 33 } 34 35 public void decrement() { 36 int counterVal = counter.decrementAndGet() 37 if(counterVal == 0) { 38 if(null != resourceCallback) { 39 resourceCallback.onTransitionToIdle(); 40 } 41 } 42 43 if(counterVal < 0) { 44 throw new IllegalArgumentException("Counter has been corrupted!"); 45 } 46 } 47 }
定义一个IdlingResource的管理类
public class EspressoIdlingResource { private static final String RESOURCE = "GLOBAL"; private static SimpleCountingIdlingResource mCountingIdlingResource = new SimpleCountingIdlingResource(RESOURCE); public static void increment() { mCountingIdlingResource.increment(); } public static void decrement() { mCountingIdlingResource.decrement(); } public static IdlingResource getIdlingResource() { return mCountingIdlingResource; } }
测试代码
public class AsyncActivityTest { @Rule public ActivityTestRule<AsyncActivity> activityRule = new ActivityTestRule<>(AsyncActivity.class); private IdlingResource idlingresource; @Before public void setUp() throws Exception { idlingresource = activityRule.getActivity().getIdlingresource(); //注册异步监听,当该idlingresource中的counter标记值为0时才进行接下来的测试代码 Espresso.registerIdlingResources(idlingresource); } @Test public void onLoadingFinished() throws Exception { // 不再需要这样的代码 // Thread.sleep(5000); // 未注册idlingResource时,立即进行test,此时异步并未结束,报错(tests failed) onView(withId(R.id.text)) .check(matches(withText("success!"))); } @After public void release() throws Exception { //我们在测试结束后取消注册,释放资源 Espresso.unregisterIdlingResources(idlingresource); } }
代码执行逻辑应该是:
1,Activity中开始异步请求 (0 to 1)
2, 异步请求中... (1)
3, 异步请求结束 (1 to 0)
4, 执行IdlingResources中执行resourceCallback.onTransitionToIdle();
5, 测试代码继续进行
Espresso.registerIdlingResources(idlingresource) 和 Thread.sleep(5000) 这两行代码都是一样的效果,阻塞住当前的测试,只不过前者更优越的是能够在异步结束之后立马执行接下来的测试代码。