Espresso之Matcher
只要使用了Espresso,那么你一定不会对withId(R.id.xxx)
和withText(R.string.xxx)
这些ViewMatchers
感到陌生。实际上无论是ViewMatchers
、RootMatchers
亦或者是Matchers
,这些本质上都是Matcher
。只是为了方便不同的使用环境进行了封装,本文将对Matcher
以及这些封装后的工具进行分析。
Matcher
所有的matcher都实现了接口Matcher
public interface Matcher<T> extends SelfDescribing {
//遍历当前视图中的 view ,匹配则返回true,失败返回false
boolean matches(Object item);
//生成本次匹配操作的描述信息
void describeMismatch(Object item, Description mismatchDescription);
}
其中matches()
是匹配的重点所在,在这里执行匹配的操作。
但是在使用过程中,我们并没有直接实现Matcher,而是实现其子类BaseMatcher所派生的封装类:TypeSafeMatcher
和BoundedMatcher
1.TypeSafeMatcher
TypeSafeMatcher
封装后的最大的区别是,增加了 isInstance()
检查当前参加匹配的目标是否符合条件。我们看一下源码,为了便于区别和阅读,只筛选出来一部分代码:
public abstract class TypeSafeMatcher<T> extends BaseMatcher<T> {
final private Class<?> expectedType;
protected TypeSafeMatcher(Class<?> expectedType) {
this.expectedType = expectedType;
}
protected abstract boolean matchesSafely(T item);
public final boolean matches(Object item) {
return item != null
&& expectedType.isInstance(item)
&& matchesSafely((T) item);
}
final public void describeMismatch(Object item, Description description) {
......
}
}
这里我们可以清楚的看出和原始的BaseMatcher
最大的区别是存储了一个expectedType
,在matchesSafely()
判断前会增加一个判断,当前 item是否是expectedType
的实例或者子类。这样可以防止出现不同类型的恰好匹配的情况,相对于BaseMatcher
是更加安全的。
我们看一个具体使用的栗子:
public static Matcher<View> withId(final int id) {
return new TypeSafeMatcher<View>() {
......
@Override
public void describeTo(Description description) {
......
}
@Override
public boolean matchesSafely(View view) {
......
return id == view.getId();
}
};
}
这是我们常用的onView(withId(R.id.xxx))
,看过前一篇文章我们应该知道onView()
这里需要的参数为Matcher
,所以我们一般使用时,会采用上面的方式书写直接使用泛型。
代码逻辑很简单,就是依次获取view的id,匹配我们输入的 R.id.xxx
,成功返回true,失败返回false。
在这里<View>泛型是用于onView()
,如果用于onData
则需要根据 data 的类型类设置泛型。
2.BoundedMatcher
上文已经说到TypeSafeMatcher
是在BaseMatcher
的基础上增加了类型的甄别,这里BoundedMatcher
就是在TypeSafeMatcher
的基础上在增加了一层安全保障。我们看一下源码,为了便于区别和阅读,只筛选出来一部分代码:
public abstract class BoundedMatcher<T, S extends T> extends BaseMatcher<T> {
private final Class<?> expectedType;
private final Class<?>[] interfaceTypes;
public BoundedMatcher(Class<?> expectedType, Class<?> interfaceType1,
Class<?>... otherInterfaces) {
//将形参类型分别保存在expectedType和interfaceTypes中
......
}
}
protected abstract boolean matchesSafely(S item);
public final boolean matches(Object item) {
if (item == null) {
return false;
}
if (expectedType.isInstance(item)) {
for (Class<?> intfType : interfaceTypes) {
if (!intfType.isInstance(item)) {
return false;
}
}
return matchesSafely((S) item);
}
return false;
}
}
原理上和TypeSafeMatcher
时没有任何区别的,只是条件上更加的严苛。这样做还有其他的便捷之处,下面我们看个栗子:
public static Matcher<View> withText(final Matcher<String> stringMatcher) {
......
return new BoundedMatcher<View, TextView>(TextView.class) {
@Override
public void describeTo(Description description) {
......
}
@Override
public boolean matchesSafely(TextView textView) {
return stringMatcher.matches(textView.getText().toString());
}
};
}
这里直接对view的类型增加了限制,所以在匹配实现中不需要关注类型项,只关注 对象的具体业务。
注意:
TypeSafeMatcher
和BoundedMatcher
使用时不要重写matches
,matches
重写后类别将失去本来的意义,如果需要重写,请直接继承BaseMatcher
。
ViewMatchers&RootMatchers&Matchers
这里会将这几部分的静态函数陈列出来,看一遍有一些印象,在之后的使用过程中可以提高工作效率,至少不用有什么想法都自己去自定义,可能已经有现成的轮子在那,况且自己做的轮子还不一定是圆的。(一下均为个人理解,如有错误请指正)
1、ViewMatchers
函数 | 功能 |
---|---|
assertThat() | 用于生成断言描述的工具 |
hasContentDescription() | 匹配具有内容描述的view |
hasDescendant() | 匹配具有特定子视图(直接或间接)的view |
hasErrorText() | 匹配getError为特定字符串的EditView |
hasFocus() | 匹配获取焦点的view |
hasImeAction() | 匹配支持输入,并且具有特定IMEAction的view |
hasLinks() | 匹配具有超链接的TextView |
hasSibling() | 匹配具有特定相邻view的view |
isAssignableFrom() | 匹配继承自特定类的view |
isChecked() | 匹配实现Checkable接口并且处于选中状态的View |
isClickable() | 匹配可以点击的view |
isCompletelyDisplayed() | 匹配全部显示在视图中的view |
isDescendantOfA() | 匹配具有特定父视图(直接或间接)的view |
isDisplayed() | 匹配显示在视图中(包括部分)的view |
isDisplayingAtLeast() | 匹配显示在视图中超过指定比值的view |
isEnabled() | 匹配当前可用(非灰色)的view |
isFocusable() | 匹配可以获取焦点的view |
isJavascriptEnabled() | 匹配开启JS的webView |
isNotChecked() | 匹配实现Checkable接口并且处于未选中状态的View |
isRoot() | 匹配本身为root的view |
isSelected() | 匹配被选中的view |
supportsInputMethods() | 匹配支持输入的View |
withChild() | 匹配具有特定直接子视图的view |
withClassName() | 匹配具有特定类名的view |
withContentDescription() | 匹配具有特定内容描述的view |
withEffectiveVisibility() | 匹配显示在屏幕上(所有父视图为Visible)的view |
withHint() | 匹配getHint为指定字符串的TextView |
withId() | 匹配具有指定id的view |
withInputType() | 匹配具有指定输入类型的EditView |
withParent() | 匹配具有特定直接父视图的view |
withResourceName() | 匹配具有指定资源名称的view |
withSpinnerText() | 匹配getSeletedItem为指定文本的view |
withTagKey() | 匹配getTag为指定值的view |
withText() | 匹配getText为指定字符串的TextView |
2、RootMatchers
函数 | 功能 |
---|---|
isDialog() | 匹配是对话框的root |
isFocusable() | 匹配拥有焦点的root |
isPlatformPopup() | 匹配是弹出窗的root |
isTouchable() | 匹配可以触摸的root |
withDecorView() | 匹配满足特定条件的root |
3、Matchers
函数 | 功能 |
---|---|
allOf() | 将所有matcher合并为一个matcher(必须满足所有matcher) |
any() | 生成一个判定是否为指定类实例或者子类的matcher |
anyOf() | 将所有matcher合并为一个matcher(满足至少一个matcher即可) |
anything() | 生成一个匹配任何对象的matcher(matches写死返回值为true) |
array() | 由n个matcher生成一个可以对应匹配array[n],中每个data的matcher(必须依次对应) |
arrayContaining() | 由n个data生成一个可以对应匹配array[n],中每个data的matcher(必须依次对应) |
arrayContainingInAnyOrder() | 由n个data生成一个可以对应匹配array[n],中每个data的matcher(不必依次对应) |
arrayWithSize() | 生成匹配指定array.size()的matcher |
both() | 将两个matcher合并成一个matcher |
closeTo() | 生成matcher匹配误差范围内的数:num∈[operand-error,operand+error] |
comparesEqualTo() | 生成matcher匹配指定value |
contains() | iterable中每一项符合对应matcher(必须依次匹配) |
containsInAnyOrder() | iterable中每一项符合对应matcher(不必依次匹配) |
containsString() | 包含特定string |
describedAs() | 更改matcher的描述 |
either() | 指定对象与指定匹配器匹配时匹配 |
empty() | collection为空 |
emptyArray() | 数组为空 |
emptyCollectionOf() | collection<class>为空 |
emptyIterable() | Iterable为空 |
emptyIterableOf() | Iterable<class>为空 |
endsWith() | String以指定字符串结尾 |
equalTo() | 封装equalTo |
equalToIgnoringCase() | string.equalTo()忽略大小写 |
equalToIgnoringWhiteSpace() | string.equalTo()忽略大小写和留白 |
eventFrom() | 匹配从指定source中派生的eventObject |
everyItem() | Iterable中任何一项都符合目标matcher |
greaterThan() | 大于指定值 |
greaterThanOrEqualTo() | 大于等于指定值 |
hasEntry() | 匹配指定Map<K , V> |
hasItem() | 匹配具有指定item的Iterable |
hasItemInArray() | 匹配具有指定item的数组 |
hasItems() | 匹配具有指定多个item的Iterable |
hasKey() | 具有特定K 的Map |
hasProperty() | 具有指定名称成员变量的对象 |
hasSize() | Collection为指定size |
hasToString() | 匹配toString为指定值的对象 |
hasValue() | 具有特定V的Map |
hasXPath() | Creates a matcher of {@link org.w3c.dom.Node}s that matches when the examined node contains a node at the specified <code>xPath</code>, with any content. |
instanceOf() | 为特定class的实例或者子类 |
is() | 封装上文matcher:equalTo |
isA() | 封装上文matcher:instanceOf |
isEmptyOrNullStringv() | ""或者空String |
isEmptyString() | ""(String) |
isIn() | 匹配指定Array中的item |
isOneOf() | 匹配列举中的一项 |
iterableWithSize() | iterable的size为指定值 |
lessThan() | 小于特定值 |
lessThanOrEqualTo() | 小于等于特定值 |
not() | 不匹配指定matcher |
notNullValue() | 不为空值 |
nullValue() | 空值 |
sameInstance() | 对象相同的 |
samePropertyValuesAs() | 具有相同属性值 |
startsWith() | String以特定字符串开始 |
stringContainsInOrder() | 具有特定一个字符串的String |
theInstance() | 对象相同的与上文sameInstance相同 |
typeCompatibleWith() | 当前class是继承自指定class |