(4.5.5.4)Espresso的进阶: OnView & onData & Matchers

来源:互联网 发布:蝴蝶战法指标公式源码 编辑:程序博客网 时间:2024/06/06 07:11

    • 一源码分析
      • 1 TypeSafeMatcher
      • 2 BoundedMatcher
    • 二工具集
      • 1 ViewMatchers View匹配器适配类
      • 2 RootMatchers 根视图匹配器的辅助类
      • 3 Matchers 对Matcher的操作集合类
      • 4 CursorMatchers
      • 5 LayoutMatchers 匹配以检测典型的布局问题
      • 6 PreferenceMatchers 匹配存储
    • 三 AdapterView的OnData
      • 1 简单类型
      • 2 官方示例
        • 21 isinstanceOfMapclass 类型校验
        • 22 hasEntryequalToSTR isitem 50参数校验
      • 3 自定义Adapter未打破继承约束
      • 4 自定义Adapter打破继承约束 usingAdapterViewProtocol
      • 5 OnData支持的筛选
      • 6 匹配 ListView 的 footerheader 视图
    • 四RecyleView
      • 1 actionOnItem源码解析
        • 11 viewHolderMatcher
      • 12 ActionOnItemViewAction
    • 参考文献

Espresso编写自动化就做三件事情:找某些东西、做某些事情、检查某些东西

  • 找到 并返回 XXInteraction交互类
    • public static ViewInteraction onView(final Matcher< View > viewMatcher)
    • public static DataInteraction onData(Matcher< ? extends Object> dataMatcher)

让我们先来看看Matchers 都有哪些API可供我们使用

  • Classes:BoundedMatcher< T, S extends T >
Some matcher sugar that lets you create a matcher for a given type but only process items of a specific subtype of that matcher.
  • Classes:CursorMatchers
A collection of Hamcrest matchers that matches a data row in a Cursor.
  • Classes:CursorMatchers.CursorMatcher
A Matcher that matches Cursors based on values in their columns.
  • Classes:LayoutMatchers
A collection of hamcrest matches to detect typical layout issues.
  • Classes:PreferenceMatchers
A collection of hamcrest matchers that match Preferences.
  • Classes:RootMatchers
A collection of matchers for Root objects.
  • Classes:ViewMatchers
A collection of hamcrest matchers that match Views.
  • Enums:ViewMatchers.Visibility
ViewMatchers.Visibility Enumerates the possible list of values for View.getVisibility()

一、源码分析

  • 所有的校验都 实现了 interface Matcher

public interface Matcher extends SelfDescribing {

    /**     * 遍历当前视图中的Views,匹配到则返回true     */    boolean matches(Object item);    /**    * 描述信息    */    void describeMismatch(Object item, Description mismatchDescription);}

当然在实际过程中,我们没有直接去实现Matcher,而是实现其子类BaseMatcher所派生的封装类。

public abstract class BaseMatcher<T> implements Matcher<T>

譬如:

//进行类型校验abstract class TypeSafeMatcher<T> extends BaseMatcher<T>

1.1 TypeSafeMatcher

直接上两个例子吧:

/*** 返回具有指定 Tag 的View*/ public static Matcher<View> withTag(final Object tag) {        return new TypeSafeMatcher<View>() {            @Override            public void describeTo(Description description) {                description.appendText("with key: " + tag);            }            @Override            public boolean matchesSafely(View view) {                return tag.equals(view.getTag());            }        };    }     /**     * 在X中的 满足Y 的 X的直系子节点     * @param parentMatcher 目标view的直接父节点     * @param childtMatcher 目标view满足     * @return     */    public static Matcher<View> childWithMatcher(            final Matcher<View> parentMatcher, final Matcher<View> childtMatcher) {        return new TypeSafeMatcher<View>() {            @Override            public void describeTo(Description description) {                description.appendText("Child with childtMatcher in parent ");                parentMatcher.describeTo(description);            }            @Override            public boolean matchesSafely(View view) {                ViewParent parent = view.getParent();                return parent instanceof ViewGroup && parentMatcher.matches(parent)                        && childtMatcher.matches(view);            }        };    }

1.2 BoundedMatcher

相对于 TypeSafeMatcher,BoundedMatcher 允许您为给定类型创建匹配器,但只能处理该匹配器的特定子类型项;
从而不需要关注类型项,而只关注 对象的具体业务

  public static Matcher<View> withText(final Matcher<String> stringMatcher) {    checkNotNull(stringMatcher);    return new BoundedMatcher<View, TextView>(TextView.class) {      @Override      public void describeTo(Description description) {        description.appendText("with text: ");        stringMatcher.describeTo(description);      }      @Override      public boolean matchesSafely(TextView textView) {        return stringMatcher.matches(textView.getText().toString());      }    };  }
  • BoundedMatcher来确保匹配器只匹配TextView类及其子类
  • 这使得很容易在BoundedMatcher.matchesSafely()中实现匹配逻辑本身:只需从TextView中获取getText()方法并将其送入下一个匹配器
  • 有一个简单的describeTo()方法的实现,它只用于生成调试输出到控制台。

二、工具集

2.1 ViewMatchers View匹配器适配类

最重要也是应用最广的匹配器,通过一个或者多个来定位层级里面的控件。
包含了大量的常见的匹配操作,大部分都是通过TypeSafeMatcher 和 BoundedMatcher实现的..

返回值 函数 示意 matchesSafely 原文 static void assertThat(String message, T actual, Matcher matcher) A replacement for MatcherAssert.assertThat that renders View objects nicely. static void assertThat(T actual, Matcher matcher) A replacement for MatcherAssert.assertThat that renders View objects nicely. static Matcher hasContentDescription() 获取具有ContentDescription的view view.getContentDescription() != null Returns an Matcher that matches Views with any content description. static Matcher hasDescendant(Matcher descendantMatcher) 获取具有“descendantMatcher满足的View”作为直接或间接子节点的VIew … Returns a matcher that matches Views based on the presence of a descendant in its view hierarchy. static Matcher hasErrorText(String expectedError) 获取是 EditText 及其子类类型的,并且getError()== expectedError 的View hasErrorText(is(expectedError)) Returns a matcher that matches EditText based on edit text error string value. static Matcher hasErrorText(Matcher stringMatcher) 获取是 EditText 及其子类类型的,并且getError()满足stringMatcher的View stringMatcher.matches(view.getError().toString()) Returns a matcher that matches EditText based on edit text error string value. static Matcher hasFocus() 获取持有焦点的View view.hasFocus() Returns a matcher that matches Views currently have focus. static Matcher hasImeAction(int imeAction) 支持输入的,并且具有指定imeAction的View … Returns a matcher that matches views that support input methods (e.g. EditText) and have the* specified IME action set in its {@link EditorInfo} static Matcher hasImeAction(Matcher imeActionMatcher) 支持输入的,并且满足imeActionMatcher的View … Returns a matcher that matches views that support input methods (e.g… static Matcher hasLinks() 获取是TextView及其子类类型的,并且具有出超链接的View textView.getUrls().length > 0 Returns a matcher that matches TextViews that have links. static Matcher hasSibling(Matcher siblingMatcher) 获取 在View层级上具有“满足siblingMatcher”作为直接或间接兄弟的View siblingMatcher.matches(parentGroup.getChildAt(i)) Returns an Matcher that matches Views based on their siblings. static Matcher isAssignableFrom(Class clazz) 获取 是clazz子类的View clazz.isAssignableFrom(view.getClass()) Returns a matcher that matches Views which are an instance of or subclass of the provided class. static Matcher isChecked() 获取实现了Checkable接口的,并且被选中的View withCheckBoxState(is(true)) Returns a matcher that accepts if and only if the view is a CompoundButton (or subtype of) and is in checked state. static Matcher isClickable() 获取可以点击的View view.isClickable() Returns a matcher that matches Views that are clickable. static Matcher isCompletelyDisplayed() 获取正在完全显示的VIew,不包括部分显示 isDisplayingAtLeast(100) Returns a matcher which only accepts a view whose height and width fit perfectly within the currently displayed region of this view. static Matcher isDescendantOfA(Matcher ancestorMatcher) 获取 拥有”满足ancestorMatcher的view”的作为父辈节点的View checkAncestors(ViewParent viewParent, Matcher ancestorMatcher) Returns a matcher that matches Views based on the given ancestor type. static Matcher isDisplayed() 获取正在显示的VIew,包括部分显示 view.getGlobalVisibleRect(new Rect())&& withEffectiveVisibility(Visibility.VISIBLE).matches(view) Returns a matcher that matches Views that are currently displayed on the screen to the user. static Matcher isDisplayingAtLeast(int areaPercentage) 获取至少有areaPercentage百分比在显示的View … Returns a matcher which accepts a view so long as a given percentage of that view’s area is not obscured by any other view and is thus visible to the user. static Matcher isEnabled() 获取 isenabled的View view.isEnabled() Returns a matcher that matches Views that are enabled. static Matcher isFocusable() 获取可以获得焦点的View view.isFocusable() Returns a matcher that matches Views that are focusable. static Matcher isJavascriptEnabled() 获取是WebView及其子类的,并且开启js的View webView.getSettings().getJavaScriptEnabled() Returns a matcher that matches WebView if they are evaluating Javascript. static Matcher isNotChecked() 获取实现了Checkable接口的,并且为被选中的View withCheckBoxState(is(false)) Returns a matcher that accepts if and only if the view is a CompoundButton (or subtype of) and is not in checked state. static Matcher isRoot() 获取是根视图的VIew view.getRootView().equals(view) Returns a matcher that matches root View. static Matcher isSelected() 获取被选中的VIew view.isSelected() Returns a matcher that matches Views that are selected. static Matcher supportsInputMethods() 获取支持输入的View view.onCreateInputConnection(new EditorInfo()) != null Returns a matcher that matches views that support input methods. static Matcher withChild(Matcher childMatcher) 获取具有 “满足childMatcher”作为直接子节点的View …childMatcher.matches(group.getChildAt(i)) A matcher that returns true if and only if the view’s child is accepted by the provided matcher. static Matcher withClassName(Matcher classNameMatcher) 获取具有 满足classNameMatcher的ClassName的View classNameMatcher.matches(view.getClass().getName()) Returns a matcher that matches Views with class name matching the given matcher. static Matcher withContentDescription(int resourceId) 获取其getContentDescription的文本 == resourceId对应的文本的View … Returns a Matcher that matches Views based on content description property value. static Matcher withContentDescription(String text) 获取其getContentDescription的文本 == text的View withContentDescription(is(text)) Returns an Matcher that matches Views based on content description property value. static Matcher withContentDescription(Matcher charSequenceMatcher) 获取其getContentDescription的文本 满足charSequenceMatcher 的View charSequenceMatcher.matches(view.getContentDescription()) Returns an Matcher that matches Views based on content description property value. static Matcher withEffectiveVisibility(ViewMatchers.Visibility visibility) 获取显示在屏幕上的View,也就是其自身和其所有祖父节点都是Visible的 … Returns a matcher that matches Views that have “effective” visibility set to the given value. static Matcher withHint(Matcher stringMatcher) 获取是TextView及其子类类型的,并且其getHint()的文本满足stringMatcher的View stringMatcher.matches(textView.getHint()) Returns a matcher that matches TextViews based on hint property value. static Matcher withHint(int resourceId) 获取是TextView及其子类类型的,并且其getText()的文本 == resourceId对应的文本的View withCharSequence(resourceId, TextViewMethod.GET_HINT) Returns a matcher that matches a descendant of TextView that is displaying the hint associated with the given resource id. static Matcher withHint(String hintText) 获取是TextView及其子类类型的,并且其getHint()的文本 == text的View withHint(is(text) Returns a matcher that matches TextView based on it’s hint property value. static Matcher withId(Matcher integerMatcher) 获取“满足integerMatcher指定的id规则”的View integerMatcher.matches(view.getId()) Returns a matcher that matches Views based on resource ids. static Matcher withId(int id) 获取指定id的view,与withId(is(int))相同,但是尝试寻找对应资源(R.id.xx),如果找到则打印出with id:R.id.xx,如果找不到对应的则打印数字或未找到 resources = view.getResources();return id == view.getId (Same as withId(is(int)), but attempts to look up resource name of the given id and use an R.id.myView style description with describeTo. static Matcher withInputType(int inputType) 获取 是EditText及其子类,并且具有指定输入键盘类型的View view.getInputType() == inputType Returns a matcher that matches InputType. static Matcher withParent(Matcher parentMatcher) 获取 具有“满足parentMatcher”的作为直接父节点的View parentMatcher.matches(view.getParent()) A matcher that accepts a view if and only if the view’s parent is accepted by the provided matcher. static Matcher withResourceName(String name) 获取具有指定资源名的View withResourceName(is(name) Returns a matcher that matches Views based on resource id names, (for instance, channel_avatar). static Matcher withResourceName(Matcher stringMatcher) 获取资源名满足stringMatcher的View stringMatcher.matches(view.getResources().getResourceEntryName(view.getId())) Returns a matcher that matches Views based on resource id names, (for instance, channel_avatar). static Matcher withSpinnerText(int resourceId) 获取是Spinner及其子类类型的,并且其getSelectedItem()的文本 == resourceId对应的文本的View … Returns a matcher that matches a descendant of Spinner that is displaying the string of the selected item associated with the given resource id. static Matcher withSpinnerText(String text) 获取是Spinner及其子类类型的,并且其getSelectedItem()的文本 == text的View withSpinnerText(is(text) Returns a matcher that matches Spinner based on it’s selected item’s toString value. static Matcher withSpinnerText(Matcher stringMatcher) 获取是Spinner及其子类类型的,并且其getSelectedItem()的文本满足stringMatcher的View stringMatcher.matches(spinner.getSelectedItem().toString()) static Matcher withTagKey(int key) 获取 具有指定名称的tag的View withTagKey(key, Matchers.notNullValue()) Returns a matcher that matches View based on tag keys. static Matcher withTagKey(int key, Matcher objectMatcher) 获取 指定名称的tag满足objectMatcher 的View objectMatcher.matches(view.getTag(key) Returns a matcher that matches Views based on tag keys. static Matcher withTagValue(Matcher tagValueMatcher) 获取 其tag满足objectMatcher 的View tagValueMatcher.matches(view.getTag() Returns a matcher that matches Views based on tag property values. static Matcher withText(Matcher stringMatcher) 获取是TextView及其子类类型的,并且其getText()的文本满足stringMatcher的View stringMatcher.matches(textView.getText().toString()) Returns a matcher that matches TextViews based on text property value. static Matcher withText(String text) 获取是TextView及其子类类型的,并且其getText()的文本 == text的View withText(is(text) Returns a matcher that matches TextView based on its text property value. static Matcher withText(int resourceId) 获取是TextView及其子类类型的,并且其getText()的文本 == resourceId对应的文本的View withCharSequence(resourceId, TextViewMethod.GET_TEXT) Returns a matcher that matches a descendant of TextView that is displaying the string associated with the given resource id

2.2 RootMatchers 根视图匹配器的辅助类

匹配root装饰视图匹配给定的视图匹配器,也就是一系列静态方法,返回指定的 Matcher

onView(withText("Text"))  .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))  .perform(click());

RootMatchers还有以下方法可以应用到其他场景:

Tables Are Cool isDialog() 匹配是对话框的根视图 Matches {@link Root}s that are dialogs (i.e. is not a window of the currently resumed* activity). isFocusable() 匹配可获取焦点的根视图 Matches {@link Root}s that can take window focus. isPlatformPopup() 匹配弹出式窗体的跟视图,如spinner,actionbar等 Matches {@link Root}s that are popups - like autocomplete suggestions or the actionbar spinner. isTouchable() 匹配可触摸的根视图 Matches {@link Root}s that can receive touch events. withDecorView(final Matcher decorViewMatcher) 指定view的跟视图 Matches {@link Root}s with decor views that match the given view matcher

下面给几个简单应用

//指定View的跟视图不在当前的Activity根布局层次中,例如ToastinRoot(withDecorView(not(mRules.getActivity().getWindow().getDecorView())))inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))

2.3 Matchers 对Matcher的操作集合类

  • allof(Matchers ) 满足Matchers 定义的所有Matcher
  • anyof(Matchers ) 至少满足Matchers 定义的任意一个Matcher
  • is(…)
  • not(…)
  • notNullValue()
  • nullValue()
  • endsWith(java.lang.String suffix)
  • startsWith(java.lang.String prefix)

2.4 CursorMatchers

Hamcrest的集合匹配器,在Cursor匹配相应的数据行

/**   * Returns a matcher that matches a {@link String} value at a given column index   * in a {@link Cursor}s data row.   * <br>   * @param columnIndex int column index   * @param value a {@link String} value to match   */  public static CursorMatcher withRowString(int columnIndex, String value) {    return withRowString(columnIndex, is(value));  }

大部分的场景,大多发生于表单或者滚动menu时:

onData(    is(instanceOf(Cursor.class)),    CursorMatchers.withRowString("job_title", is("Barista")));

2.5 LayoutMatchers 匹配以检测典型的布局问题

例如匹配具有椭圆形文本的TextView元素。

如果文本太长,无法适应TextView,它可以是椭圆形(’Too long’显示为’Too l …’或’… long’)或切断(’Too long“显示为”Too l“)。

虽然在某些情况下可以接受,但通常表示不好的用户体验

2.6 PreferenceMatchers 匹配存储

Preference组件其实就是Android常见UI组件与SharePreferences的组合封装实现

onData(Matchers.<Object>allOf(PreferenceMatchers.withKey("setting-name"))).perform(click());

PreferenceMatchers还有以下方法可以应用到其他场景

withSummary(final int resourceId)withSummaryText(String summary)withSummaryText(final Matcher<String> summaryMatcher)withTitle(final int resourceId)withTitleText(String title)withTitleText(final Matcher<String> titleMatcher)isEnabled()

onView是根据View的相关属性来找到Interaction交互类
OnData则是根据 Data的相关属性来找到Interaction交互类

三、 AdapterView的OnData

  • ViewInteraction: 关注于已经匹配到的目标控件。通过onView()方法我们可以找到符合匹配条件的唯一的目标控件,我们只需要针对这个控件进行我们需要的操作

    • onView()
    • Matcher< View>: 构造一个针对于View匹配的匹配规则
  • DataInteraction: 关注于AdapterView的数据。由于AdapterView的数据源可能很长,很多时候无法一次性将所有数据源显示在屏幕上,因此我们主要先关注AdapterView中包含的数据,而非一次性就进行View的匹配。

    • onData
    • Matcher< ? extends Object>: 构造一个针对于Object(数据)匹配的匹配规则

onData 只适用于 AdapterView及其派生类,不适用于 RecyleView。

AdapterView是一种通过Adapter来动态加载数据的界面元素。我们常用的ListView, GridView, Spinner等等都属于AdapterView。不同于我们之前提到的静态的控件,AdapterView在加载数据时,可能只有一部分显示在了屏幕上,对于没有显示在屏幕上的那部分数据,我们通过onView()是没有办法找到的。

  • Matcher< ? extends Object>的构建规则:
    • 类型校验 : 确认AdapterView
    • 参数校验:确认item

注:初始化时就显示在屏幕上的adapter中的view你也可以不适用onData()因为他们已经被加载了。然而,使用onDta()会更加安全。

提醒:在打破了继承约束(尤其是getItem()的API)实现了AdatpterView的自定义view中onData()是有问题的。在这中情况下,做好的做法就是重构应用的代码。如果不重构代码,你也可以实现自定义的AdapterViewProtocol来实现。查看Espresso的AdapterViewProtocols 来查看更多信息。

  • 更多AdapterViewProtocol

3.1 简单类型

  • 类型校验
  • 参数校验
onData(allOf(is(instanceOf(String.class)),is("Americano"))).perform(click());

3.2 官方示例

这里写图片描述
如上 activity 包含一个 ListView,它基于一个为每一行提供一个 ​Map

onData(allOf(        is(instanceOf(Map.class)),         hasEntry(equalTo("STR"), is("item: 50)       )).perform(click());

3.2.1 is(instanceOf(Map.class)) 类型校验

限制搜索 AdapterView 中任意条目的条件为一个 Map。

在此例子中,ListView 的所有行都满足条件。但我们想要点击指定的条目 “item: 50”,所以我们需要继续缩小范围:

3.2.2 hasEntry(equalTo(“STR”), is(“item: 50)参数校验

 hasEntry(equalTo("STR"), is("item: 50)

优化:

这个 Matcher< String, Object > 会匹配所有包含任意键,值=“item: 50” 的 Map 。鉴于查找此条目的代码较长,而且我们希望在其他地方重用它,我们可以写一个自定义的 matcher “withItemContent”:

public static Matcher<Object> withItemContent(String expectedText) {  checkNotNull(expectedText);  return withItemContent(equalTo(expectedText));}public static Matcher<Object> withItemContent(Matcher 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);        }  };}

现在点击该条目的代码很简单了:

onData(withItemContent("item: 50")).perform(click());

此测试的完整代码请查看 AdapterViewText#testClickOnItem50 和自定义匹配器。

3.3 自定义Adapter:未打破继承约束

使用BoundedMatcher实现:

  • 类型校验
  • 参数校验

其实没啥说的,直接给个示例吧:

//根据销售机会名称查找到List中的item,并点击onData(SaleOppMatcher.searchMainItemWithName(oppName)).perform(click());
    /**     * 查找指定搜索条件  销售机会匹配     * @param name 需要搜索的字     */    public static Matcher<Object> searchMainItemWithName(final String name) {        return new BoundedMatcher<Object, SalesOpp>(SalesOpp.class) {            @Override            protected boolean matchesSafely(SalesOpp item) {                return item != null                        && !TextUtils.isEmpty(item.content)                        && item.content.equals(name);            }            @Override            public void describeTo(Description description) {                description.appendText("SalesOpp has Name: " + name);            }        };    }

3.4 自定义Adapter:打破继承约束 usingAdapterViewProtocol

//根据客户拜访名称查找到List中的item,并点击onData(LegWorkMatcher.searchMainItemWithName(legName)).perform(click());
  • (4.5.5.4#)Espresso的进阶: AdapterViewProtocol

3.5 OnData支持的筛选

onData我们使用了data进行了筛选,完全屏蔽了View,那么现在问题来了:

假设一个页面里有多个ListView,譬如“ViewPager+ListView的实现”

如果继续用上述方法,会出现:

android.support.test.espresso.AmbiguousViewMatcherException: ‘is assignable from class: class android.widget.AdapterView’ matches multiple views in the hierarchy. Problem views are marked with ‘*MATCHES*’ below.

大意就是说,有多个AdapterView在界面里,那么结局方案是:

  • inAdapterView(Matcher adapterMatcher)
onData(...).inAdapterView(allOf(isAssignableFrom(AdapterView.class),isDisplayed())).perform(click());onData(...).inAdapterView(allOf(withId(R.id.list),isDisplayed())).perform(click());

现在我们详细的看看DataInteraction还支持那些筛选,来辅助onData:

Tables 示意 适用场景 inAdapterView(Matcher< View > adapterMatcher) 选择 满足adapterMatcher的adapterview 来执行onData操作 当前界面有多个adapterview,导致错误 onChildView(Matcher< View> childMatcher) 匹配”onData所匹配的item视图”中的指定子视图 需要点击item中的某个子view atPosition(Integer atPosition) 选中匹配的Adapter的第几项,默认不选择,可以通过atPosition方法设定 onData只定义“类型校验”, onData(is(instanceOf(String.class)).atPosition(0)… inRoot(Matcher< Root> rootMatcher) 在指定跟视图中来执行onData操作 对话框里的adapterview
  • PullToRefreshListView 如何使用inAdapterView?

PullToRefreshListView里面的ListView实际上是嵌套了一个id为android.R.id.list的ListView,因此可以用这个ID来进行匹配。如果单一匹配规则还不够精细,可以再从其他方面构造复合匹配规则。比如针对这种情况我采用了:

onData(allOf(is(instanceOf(TeacherModel.class)),teacherHasName(VALUE_TEACHER_NAME_TARGET))).inAdapterView(allOf(withId(android.R.id.list), isDisplayed())).perform(click());

3.6 匹配 ListView 的 footer/header 视图

header 和 footer 通过 addHeaderView/addFooterView API 添加到 ListView 中。为了能够使用 Espresso.onData 加载它们,确保使用预置的值来设置数据对象(第二个参数)。

public static final String FOOTER = "FOOTER";...View footerView = layoutInflater.inflate(R.layout.list_item, listView, false);((TextView) footerView.findViewById(R.id.item_content)).setText("count:");((TextView) footerView.findViewById(R.id.item_size)).setText(String.valueOf(data.size()));listView.addFooterView(footerView, FOOTER, true);

然后,你可以写一个匹配器来匹配此对象:

import static org.hamcrest.Matchers.allOf;import static org.hamcrest.Matchers.instanceOf;import static org.hamcrest.Matchers.is;@SuppressWarnings("unchecked")public static Matcher<Object> isFooter() {  return allOf(is(instanceOf(String.class)), is(LongListActivity.FOOTER));}

在测试中很轻易就能加载该视图:

import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;import static com.google.android.apps.common.testing.ui.espresso.sample.LongListMatchers.isFooter;public void testClickFooter() {  onData(isFooter())    .perform(click());  ...}

请在 AdapterViewtest#testClickFooter 中查看完整示例代码。

四、RecyleView

RecyclerViews 和 AdapterViews 的工作原理不同,因此onData()不适用于RecyleView

如果想要与RecyleView交互,请引入“espresso-contrib”,里边包含一系列的Actions可以用于滚动和点击

//TODO 根据android.support.test.espresso.contrib.RecyclerViewActions揣摩RecyclerView的macher规则

4.1 actionOnItem源码解析

  public static <VH extends ViewHolder> PositionableRecyclerViewAction actionOnItem(      final Matcher<View> itemViewMatcher, final ViewAction viewAction) {    Matcher<VH> viewHolderMatcher = viewHolderMatcher(itemViewMatcher);    return new ActionOnItemViewAction<VH>(viewHolderMatcher, viewAction);  }

4.1.1 viewHolderMatcher

  /**   * Creates matcher for view holder with given item view matcher.   *   * @param itemViewMatcher a item view matcher which is used to match item.   * @return a matcher which matches a view holder containing item matching itemViewMatcher.   */  private static <VH extends ViewHolder> Matcher<VH> viewHolderMatcher(      final Matcher<View> itemViewMatcher) {    return new TypeSafeMatcher<VH>() {      @Override      public boolean matchesSafely(RecyclerView.ViewHolder viewHolder) {        return itemViewMatcher.matches(viewHolder.itemView);      }      @Override      public void describeTo(Description description) {        description.appendText("holder with view: ");        itemViewMatcher.describeTo(description);      }    };  }

4.1.2 ActionOnItemViewAction

private static final class ActionOnItemViewAction<VH extends ViewHolder> implements      PositionableRecyclerViewAction {    private final Matcher<VH> viewHolderMatcher;    private final ViewAction viewAction;    private final int atPosition;    private final ScrollToViewAction<VH> scroller;    private ActionOnItemViewAction(Matcher<VH> viewHolderMatcher, ViewAction viewAction) {      this(viewHolderMatcher, viewAction, NO_POSITION);    }    private ActionOnItemViewAction(Matcher<VH> viewHolderMatcher, ViewAction viewAction,        int atPosition) {      this.viewHolderMatcher = checkNotNull(viewHolderMatcher);      this.viewAction = checkNotNull(viewAction);      this.atPosition = atPosition;      this.scroller = new ScrollToViewAction<VH>(viewHolderMatcher, atPosition);    }    @SuppressWarnings("unchecked")    @Override    public Matcher<View> getConstraints() {      return allOf(isAssignableFrom(RecyclerView.class), isDisplayed());    }    @Override    public PositionableRecyclerViewAction atPosition(int position) {      checkArgument(position >= 0, "%d is used as an index - must be >= 0", position);      return new ActionOnItemViewAction<VH>(viewHolderMatcher, viewAction, position);    }    @Override    public String getDescription() {      if (atPosition == NO_POSITION) {        return String.format("performing ViewAction: %s on item matching: %s",            viewAction.getDescription(), viewHolderMatcher);      } else {        return String.format("performing ViewAction: %s on %d-th item matching: %s",            viewAction.getDescription(), atPosition, viewHolderMatcher);      }    }    @Override    public void perform(UiController uiController, View root) {      RecyclerView recyclerView = (RecyclerView) root;      try {        scroller.perform(uiController, root);        uiController.loopMainThreadUntilIdle();        // the above scroller has checked bounds, dupes (maybe) and brought the element into screen.        int max = atPosition == NO_POSITION ? 2 : atPosition + 1;        int selectIndex = atPosition == NO_POSITION ? 0 : atPosition;        List<MatchedItem> matchedItems = itemsMatching(recyclerView, viewHolderMatcher, max);        actionOnItemAtPosition(matchedItems.get(selectIndex).position, viewAction).perform(            uiController, root);        uiController.loopMainThreadUntilIdle();      } catch (RuntimeException e) {        throw new PerformException.Builder().withActionDescription(this.getDescription())            .withViewDescription(HumanReadables.describe(root)).withCause(e).build();      }    }  }

参考文献

  • Android ViewMatchers
  • http://blog.csdn.net/qq744746842/article/category/6088564
0 0
原创粉丝点击