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) 这两行代码都是一样的效果,阻塞住当前的测试,只不过前者更优越的是能够在异步结束之后立马执行接下来的测试代码。

 

posted on 2017-12-27 17:25  CrazyQA  阅读(1635)  评论(0编辑  收藏  举报

导航