【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 <>)
当然系统给出你详细的信息,让你进行排查:
+----->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
【Android测试】【第十九节】Espresso——API详解的更多相关文章
- “全栈2019”Java多线程第十九章:死锁详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java异常第十九章:RuntimeException详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...
- “全栈2019”Java第二十九章:数组详解(中篇)
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- Android Developer -- Bluetooth篇 开发实例之四 API详解
http://www.open-open.com/lib/view/open1390879771695.html 这篇文章将会详细解析BluetoothAdapter的详细api, 包括隐藏方法, 每 ...
- Android 音视频开发(六): MediaCodec API 详解
在学习了Android 音视频的基本的相关知识,并整理了相关的API之后,我们应该对基本的音视频有一定的轮廓了. 下面开始接触一个Android音视频中相当重要的一个API: MediaCodec.通 ...
- 第三十九课:requestAnimationFrame详解
大家应该都知道,如果一个页面运行的定时器很多,无论你怎么优化,最后肯定会超过指定时间才能完成动画.定时器越多,延时越严重. 为此,YUI,kissy等采用中央队列的方式,将定时器减少至一个.浏览器厂商 ...
- jQuery 源码分析(十九) DOM遍历模块详解
jQuery的DOM遍历模块对DOM模型的原生属性parentNode.childNodes.firstChild.lastChild.previousSibling.nextSibling进行了封装 ...
- 大白话5分钟带你走进人工智能-第二十九节集成学习之随机森林随机方式 ,out of bag data及代码(2)
大白话5分钟带你走进人工智能-第二十九节集成学习之随机森林随机方式 ,out of bag data及代码(2) 上一节中我们讲解了随机森林的基本概念,本节的话我们讲解随机森 ...
- 第三百二十九节,web爬虫讲解2—urllib库爬虫—ip代理—用户代理和ip代理结合应用
第三百二十九节,web爬虫讲解2—urllib库爬虫—ip代理 使用IP代理 ProxyHandler()格式化IP,第一个参数,请求目标可能是http或者https,对应设置build_opener ...
随机推荐
- js中的事件委托或是事件代理详解
起因: 1.这是前端面试的经典题型,要去找工作的小伙伴看看还是有帮助的: 2.其实我一直都没弄明白,写这个一是为了备忘,二是给其他的知其然不知其所以然的小伙伴们以参考: 概述: 那什么叫事件委托呢?它 ...
- windows7 启用管理员账户
在虚拟机中安装了windows7,方便使用qq,使用百度云,office等常用的软件.虚拟机使用的oracle的VirtualBox,个人使用体验不错,最棒的功能是能将物理机中的磁盘映射到虚拟机中网络 ...
- LeetCode 219 Contains Duplicate II
Problem: Given an array of integers and an integer k, find out whether there are two distinct indice ...
- LeetCode之344. Reverse String
------------------------------- Java也可以实现一行代码反转字符串哦 AC代码如下: public class Solution { public String re ...
- java获取汉字拼音首字母 --转载
在项目中要更能根据某些查询条件(比如姓名)的首字母作为条件进行查询,比如查一个叫"李晓明"的人,可以输入'lxm'.写了一个工具类如下: import java.io.Unsupp ...
- checkbox实现互斥
<html xmlns="http://www.w3.org/1999/xhtml" > <head> <title>标题页-学无忧(www.x ...
- CSS中定义CLASS时,中间有空格和每空格的区别
css选择器的格式,规定不带空格的选择条件之间是“且”关系,带空格的是“父子”关系,并且可以是非直接的“父子”关系 <style> .e1.e2 { background-color:ye ...
- [LeetCode] Best Time to Buy and Sell Stock
Say you have an array for which the ith element is the price of a given stock on day i. If you were ...
- PIL中分离通道发生“AttributeError: 'NoneType' object has no attribute 'bands'”
解决方法: 这个貌似是属于一个bug 把Image.py中的1500行左右的split函数改成如下即可: def split(self): "Split image into bands&q ...
- python中各种结构的复杂度
list The Average Case assumes parameters generated uniformly at random. Internally, a list is repres ...