不错的官方文档翻译(含作者见解)
这篇文章翻译简直太棒了,本文的内容都来自对该文章所记录的笔记。
Android自动化测试--Espresso框架使用
Android官网,_科学上网也访问不了跪求原因
如何使用Espresso
- 在module级别的Gradle中添加以下配置
android {
defaultConfig {
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
2.添加依赖
dependencies {
androidTestCompile 'com.android.support:support-annotations:23.1.1'
androidTestCompile 'com.android.support.test:runner:0.4.1'
androidTestCompile 'com.android.support.test:rules:0.4.1'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1'
}
3.测试代码编写
一般来说我们在main/java中放置主要的业务逻辑代码,在 androidTest/java放置测试代码.
src/
androidTest/java ----这里存放instrumentation test相关的代码
main/java ----这里存放工程代码
建议测试代码的分类与被测试类放置在相同的包目录下,例如待测试的类为src/main/java/package-name/A.java,那么测试类可以放置在src/androidTest/java/package-name/ATest.java目录下,测试类名称以Test作为后缀.
Espresso的一些语法
onView() 查找元素,其完整的方法签名如下:
public static ViewInteraction onView(final Matcher<View> viewMatcher) {}
这个方法接收一个Matcher<View>类型的入参,返回一个ViewInteraction对象,其所做的事情就是根据Matcher<View>所指定的条件,在当前UI页面上寻找符合条件的View,并且把相应的View返回出来。这样说还是比较抽象,我们可以用一个具体的例子加以说明。
当我们在实现布局的时候,每个控件都会有一些特殊的属性来确定其唯一性,比如最常用的R.id。Matcher<View>支持通过控件的唯一ID来从当前页面上寻找目标控件,对应的方法为withId(),该方法定义如下:
public static Matcher<View> withId(final int id) {}
大家可以看到,该方法接收了一个int类型的入参,返回了一个Matcher<View>对象,于是,采用如下写法:
onView(withId(id));
我们就能在当前页面找到指定ID所对应的目标控件了。
实际上,Espresso提供了很多方法来让我们自定义我们的查找条件。比如我们可以通过withText()方法来寻找显示了指定文案的控件等等。具体支持的Matcher类型可以参考Espresso cheat sheet。
需要提醒大家一点的是,onView()方法在根据匹配条件进行查找时,它的目标是找到唯一的一个目标控件。如果我们制定的匹配条件有多个控件可以匹配(比如复用了layout的布局,或者显示相同文字的TextView等),该方法会抛出一个AmbiguousViewMatcherException异常,因此我们在构造匹配条件时,一定要确保能查找到的目标控件是唯一的。
如果单一的匹配条件无法精确地匹配出来唯一的控件,我们可能还需要额外的匹配条件,此时可以用allOf()方法来进行复合匹配条件的构造:
onView(allOf(withId(id), withText(text)))
以上代码可以查找ID为id同时显示的文字内容为text的控件。这里需要注意的是,为了保证自动化测试的效率,我们应尽可能减少匹配条件的数量。如果用一个匹配条件能够满足我们的需求,我们也就没有必要再用allOf()来构造复合匹配条件了。
操作元素
public ViewInteraction perform(final ViewAction... viewActions) {}
该方法定义在ViewInteraction类里面。还记得onView()方法的返回值么?yes,正是一个ViewInteraction对象。因此,我们可以在onView()方法找到的元素上直接调用perform()方法进行一系列操作:
onView(withId(id)).perform(click())
如上代码对onView()查询到的元素做了一次点击的操作。请注意,perform()方法的入参是变长参数,也就意味着,我们可以依次对某个元素做多个操作:
onView(withId(id)).perform(click(), replaceText(text), closeSoftKeyboard())
以上代码对目标元素依次做了点击、输入文本、关闭输入法键盘的操作。这是一个典型的填写表单的行为。
检查结果
到目前为止,我们已经能找到元素,也能够对元素进行一些操作了!接下来我们需要检查一下这些操作的结果是否符合我们的预期。
Espresso提供了一个check()方法用来检测结果:
public ViewInteraction check(final ViewAssertion viewAssert) {}
该方法接收了一个ViewAssertion的入参,该入参的作用就是检查结果是否符合我们的预期。一般来说,我们可以调用如下的方法来自定义一个ViewAssertion:
public static ViewAssertion matches(final Matcher<? super View> viewMatcher) {}
这个方法接收了一个匹配规则,然后根据这个规则为我们生成了一个ViewAssertion对象!还记得Matcher这个类型么!!是的,这就是onView()方法的入参!实际上他们是同一个类型,其使用方法也是完全一致的。
比如,我想检查一下指定id的TextView是否按照我的预期显示了一段text文本,那么我就可以这样写:
onView(withId(id)).check(matches(withText(text)))
简洁明了。ViewAssertion的支持也可以参照这个Espresso cheat sheet。
AdapterView
对于类似ListView这种有UI复用的元素来说,只是通过onView()就显得复杂了一点,我们来看一下针对这种情况应有何种方案。
AdapterView是一种通过Adapter来动态加载数据的界面元素。我们常用的ListView, GridView, Spinner等等都属于AdapterView。不同于我们之前提到的静态的控件,AdapterView在加载数据时,可能只有一部分显示在了屏幕上,对于没有显示在屏幕上的那部分数据,我们通过onView()是没有办法找到的。
对于AdapterView,Espresso提供了如下方法用来查找元素:
/**
* Creates an {@link DataInteraction} for a data object displayed by the application. Use this
* method to load (into the view hierarchy) items from AdapterView widgets (e.g. ListView).
*
* @param dataMatcher a matcher used to find the data object.
*/
public static DataInteraction onData(Matcher<? extends Object> dataMatcher) {}
我们首先来研究一下这个方法的返回值。从以上定义可以看出,该方法返回了一个DataInteraction对象,还记得onView()方法返回的ViewInteraction对象么?这两者的区别可以大概描述为:
- ViewInteraction: 关注于已经匹配到的目标控件。通过onView()方法我们可以找到符合匹配条件的唯一的目标控件,我们只需要针对这个控件进行我们需要的操作。
- DataInteraction: 关注于AdapterView的数据。由于AdapterView的数据源可能很长,很多时候无法一次性将所有数据源显示在屏幕上,因此我们主要先关注AdapterView中包含的数据,而非一次性就进行View的匹配。
我们再来研究一下这个方法的入参。从以上定义看出,该方法接收了一个Matcher<? extends Object>的参数,该参数用来指定一个匹配规则。还记得onView()的入参么?是一个Matcher<View>对象。从类型上来看,这两者的区别也不言而喻:
- Matcher<View>: 构造一个针对于View匹配的匹配规则;
- Matcher<? extends Object>: 构造一个针对于Object(数据)匹配的匹配规则。
从以上对比可以看出,我们在使用onData()方法对AdapterView进行测试的时候,我们的思路就转变成了首先关注这个AdapterView的具体数据,而不是UI上呈现的内容。当然,我们最终的目标还是要找到目标的UI元素,但是我们是通过其数据源来进行入手的。
寻找数据
那么,接下来,我们就要学习如何去寻找我们需要的数据了!显然,要想找到我们需要的数据,就需要构造一个onData()所使用的Matcher对象,而这个对象的构造和使用实际上和之前我们所用的针对于View的Matcher大概雷同。比如,我们可以指定单一条件:
onData(is(instanceOf(MyObject.class)))
表示我们需要找一个AdapterView,其数据源的类型是MyObject(这是一个自定义的类)。当然了,我们肯定还是需要更加精确地去寻找一个AdapterView中的指定条目,于是我们可以采用allOf()来构造一个符合匹配条件:
onData(allOf(is(instanceOf(MyObject.class)), myCustomMatcher()))
如上代码便使用allOf()方法构造了一个符合匹配规则。而上面的myCustomMatcher()方法构造了一个自定义的Matcher,我们可以采用自己的自定义Matcher来更加精准地进行数据的匹配。
//TODO