zoukankan      html  css  js  c++  java
  • 【Android测试】【第十九节】Espresso——API详解

     版权声明:本文出自胖喵~的博客,转载必须注明出处。

      转载请注明出处:http://www.cnblogs.com/by-dream/p/5997557.html 

    前言


      Espresso的提供了不少API支持使用者来和界面元素进行交互,但同时它又阻止使用者直接获取Activity和View,它为的就是想保持让这些对象在UI线程中执行,以防发生线程不安全的情况。因此在Espresso中我们看不到getView、getCurrentActivity类似这样的方法。但是我们可以通过实行自己的ViewAction和ViewAssertion来安全的操作View。这就是Espresso的思想。

    认识组件


      Espresso:与视图交互的入口(通过onView和onData),还包含一些不绑定到任何元素上的API(例如pressBack)。

      ViewMatchers: 实现Matcher<? super View>接口对象的集合,可以将一个或者多个传递给onView,从而定位当前视图中的元素。

      ViewActions:可以传递到ViewInteraction.perform方法的集合(例如click)。

      ViewAssertions:可以传递到ViewInteraction.check方法的集合。 大多数时候需要matches断言,即使用View Matcher来和当前选择的视图的状态进行断言。

      举例:

      

    定位元素onView


      onView使用的是一个hamcrest匹配器,该匹配器只匹配当前视图层次结构中的一个(且只有一个)视图。如果你不熟悉hamcrest匹配器,建议先看看这个。通常情况下一个控件的id是唯一的,但是有些特定的视图是无法通过R.id拿到,因此我们就需要访问Activity或者Fragment的私有成员找到拥有R.id的容器。有的时候也需要使用ViewMatchers来缩小定位的范围。

      最简单的onView就是这样的形式:

    onView(withId(R.id.my_view))

      有的时候多个视图之间共享R.id值,当这种情况下,我们调用系统会抛出这样的异常AmbiguousViewMatcherException:

    java.lang.RuntimeException:
    com.google.android.apps.common.testing.ui.espresso.AmbiguousViewMatcherException:
    This matcher matches multiple views in the hierarchy: (withId: is <123456789>)

      当然系统给出你详细的信息,让你进行排查:

    +----->SomeView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true,
    is-focused=false, is-focusable=false, enabled=true, selected=false, is-layout-requested=false, text=, root-is-layout-requested=false, x=0.0, y=625.0, child-count=1}
    ****MATCHES****
    |
    +------>OtherView{id=123456789, res-name=plus_one_standard_ann_button, visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true, window-focus=true,
    is-focused=false, is-focusable=true, enabled=true, selected=false, is-layout-requested=false, text=Hello!, root-is-layout-requested=false, x=0.0, y=0.0, child-count=1}
    ****MATCHES****

      通过上面的信息对比,我们可以发现text字段是不一致的,因此我们就可以根据这个组合匹配来缩小定位范围,方法如下:

    onView(allOf(withId(R.id.my_view), withText("Hello!")))

      你也可以使用这样的方法:

    onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))

      对于大部分的控件,使用上述的方法就可以搞定了,如果你发现使用“withText”或“withContentDescription”都无法定位到元素的时候,谷歌建议你可以给开发提一个可访问性的bug了。

      下面这种情况,出现了很多同样的数字,但是它旁边有可以识别出的唯一元素,这个时候我们就也可以使用hasSibling来进行筛选:

      

    onView(allOf(withText("7"), hasSibling(withText("item: 0")))).perform(click());

       另外说两个常用的menu,如果是 overflow menu也就是下面这种情况的下:

      

      需要使用:

    openActionBarOverflowOrOptionsMenu(getInstrumentation().getTargetContext());

      如果是下面这样的:

      

      使用:

    openContextualActionModeOverflowMenu();

      注意:如果目标视图在AdapterView(例如ListView,GridView,Spinner)中,onView方法可能无法正常工作,这个时候需要用到onData方法。 

      

    定位元素onData


       假设一个Spinner的控件,我们要点击“Americano”,我们使用默认的Adaptor,它的字段默认是String的,因此当我们要进行点击的时候,就可以使用如下方法: 

    onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());

      假设是一个Listview,我们需要点击Listview中第二个item的按钮,那么我们需要这样写:

    onData(Matchers.allOf())
            .inAdapterView(withId(R.id.photo_gridview)) // listview的id
            .atPosition(1)                              // 所在位置
            .onChildView(withId(R.id.imageview_photo))  // item中子控件id
            .perform(click());

     

     

    执行操作


      当你获取到了目标的控件后,就可以使用perform来执行操作了。例如一个点击操作:

    onView(...).perform(click());

      也可以通过一个命令执行多个操作:

    // 输入hello,并且点击
    onView(...).perform(typeText("Hello"), click());

      当你操作的对象如果是ScrollView,在执行其他操作(例如click、typeText)之前,必须确保当前的控件是出现在当前的可视范围的,若没有可以使用scrollTo的方法:

    onView(...).perform(scrollTo(), click());

      如果可视范围已经出现了该元素,scrollTo将不起作用,因此当你的屏幕分辨率大或小的时候,都可以放心安全地使用它。

    校验


      使用check方法可以断言当前选择的界面, 常用的断言是matches,它使用ViewMatcher来断言当前选定视图的状态。例如,要检查视图中是否包含“Hello”这个字符串:

    onView(...).check(matches(withText("Hello")));

      千万不要使用下面这样的方法做断言,谷歌是不推荐这样的:

    // Don't use assertions like withText inside onView.
    onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));

      所以当我们需要断言一个指定的内容是否在AdapterView当中的时候,我们需要做一些特殊的处理。做法就是找到AdapterView,然后访问它的内部元素,这里不适用onData,而是使用onView和我们自己写的matcher来进行处理。我们自定义一个matcher叫withAdaptedData,实现如下:

    private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) {
      return new TypeSafeMatcher<View>() {
    
        @Override
        public void describeTo(Description description) {
          description.appendText("with class name: ");
          dataMatcher.describeTo(description);
        }
    
        @Override
        public boolean matchesSafely(View view) {
          if (!(view instanceof AdapterView)) {
            return false;
          }
          @SuppressWarnings("rawtypes")
          Adapter adapter = ((AdapterView) view).getAdapter();
          for (int i = 0; i < adapter.getCount(); i++) {
            if (dataMatcher.matches(adapter.getItem(i))) {
              return true;
            }
          }
          return false;
        }
      };
    }

      然后我们就可以使用它来进行断言了:

    // 当list当中是否存在一个bryan的item,就断言失败
    onView(withId(R.id.list)).check(matches(not(withAdaptedData(withItemContent("bryan")))));

      因为里面还用到了一个withItemContent,我们也需要实现它:

    public static Matcher<Object> withItemContent(final Matcher<String> itemTextMatcher) {
        // use preconditions to fail fast when a test is creating an invalid matcher.
        checkNotNull(itemTextMatcher);
        return new BoundedMatcher<Object, Map>(Map.class) {
          @Override
          public boolean matchesSafely(Map map) {
            return hasEntry(equalTo("STR"), itemTextMatcher).matches(map);
          }
          @Override
          public void describeTo(Description description) {
            description.appendText("with item content: ");
            itemTextMatcher.describeTo(description);
          }
        };
      }

      所以当我们要断言时,如果遇到了一些没有实现的内容,就需要我们重写matcher了。

    参考图


      下面这幅图,我们在写代码中可以快速查看,这里面包含了大部分我们经常用的API:

     

      细心的人应该能看到图中有intent相关的内容,这部分内容我目前没有用到,因此也没有深入的了解。有兴趣的可以自己看看。

      参考链接:https://google.github.io/android-testing-support-library/docs/espresso/intents/index.html

  • 相关阅读:
    机器学习python实战----决策树
    机器学习python实战----手写数字识别
    机器学习python实战----k近邻算法
    斯坦福2014机器学习笔记八----机器学习系统的设计
    斯坦福2014机器学习笔记七----应用机器学习的建议
    斯坦福2014机器学习笔记六----神经网络(二)
    Global Game Jam 2019 深圳站 个人总结
    加法乘法线段树模板
    线段树模板题
    单调栈和单调队列入门
  • 原文地址:https://www.cnblogs.com/by-dream/p/5997557.html
Copyright © 2011-2022 走看看