官方文档
https://google.github.io/android-testing-support-library/docs/espresso/setup/index.html
参考一系列文章 : https://segmentfault.com/a/1190000004392396
我关心的是我能在UI上看到我希望看到的结果。基于此, 各个测试用例的一个通用的思路就是:
找到某个元素,做一些操作,检查结果。
这里包含了三个流程:
找元素:找到UI上测试所针对的元素;
做操作:给这个元素做一些操作;
检查结果:这个元素做出了我期望的行为。
再直观一点,我向一个表单输入一段文字,那么整个过程就可以描述为:
找元素:找到EditText;
做操作:向EditText输入字符串;
检查结果:EditText显示了我输入的字符串。
以上三个小步骤实际上也是我作为用户在使用一个APP的时候所遵循的流程。而我们的测试也是基本遵循这样一个流程的。
添加依赖
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
androidTestCompile 'com.android.support.test:runner:0.5'
加入到同一个文件的build.gradle下面一行android.defaultConfig:
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
示例的build.gradle文件
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion "22"
defaultConfig {
applicationId "com.my.awesome.app"
minSdkVersion 10
targetSdkVersion 22.0.1
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
// App's dependencies, including test
compile 'com.android.support:support-annotations:22.2.0'
// Testing-only dependencies
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}
@Test: 标识一个测试方法。一个测试类中可以有多个测试方法,每个测试方法需要用一个@Test注解来标识。
@Rule: 简单来说,是为各个测试方法提供一些支持。
具体来说,比如我需要测试一个Activity,那么我可以在@Rule注解下面采用一个ActivityTestRule,该类提供了对相应Activity的功能测试的支持。
该类可以在@Before和@Test标识的方法执行之前确保将Activity运行起来,
并且在所有@Test和@After方法执行结束之后将Activity杀死。
在整个测试期间,每个测试方法都可以直接对相应Activity进行修改和访问。
例:
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
发现其R.id看法是简单的:
onView(withId(R.id.my_view))
有时,R.id值的多个视图之间共享。
当发生这种情况的尝试以使用特定的
R.id给你一个AmbiguousViewMatcherException(例如)。
异常消息为您提供了当前视图层次,你可以搜索并找到匹配的非唯一的意见的文本表示R.id:
java.lang.RuntimeException:
com.google.android.apps.common.testing.ui.espresso.AmbiguousViewMatcherException:
This matcher matches multiple views in the hierarchy: (withId: is <123456789>)
通过对意见的各种属性看,你会发现唯一识别的特性(在上面的例子中,一个视图具有文本“Hello!”)。您可以使用此通过结合匹配器来缩小搜索范围:
onView(allOf(withId(R.id.my_view), withText("Hello!")))
您还可以使用not扭转任何匹配器:
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
见ViewMatchers由咖啡提供的视图的匹配。
注意:在运行良好的应用程序,所有视图,用户可以使用都应包含说明性文字互动或有内容描述(见Android的易用性原则如果您在使用“withText'或'无法缩小的OnView由搜索。 withContentDescription',可以考虑把它当作一个辅助的错误。
注意:用最少的描述匹配的找到你正在寻找的一种看法。不要过分指定为这将迫使该框架做更多的工作是必要的。例如,如果一个视图是由它的文本唯一标识,则不需要指定该观点也与分配TextView。对于很多意见R.id认为,应该足够了。
注意:如果目标视图内的AdapterView(例如ListView,GridView,Spinner)的onView方法可能无法正常工作,并建议使用onData方法来代替。
在视图上执行动作
当找到了目标视图的适当匹配,则能够进行ViewAction利用上IT方面perform的方法。
例如,为了点击查看:
onView(...).perform(click());```
一个执行呼叫,您可以执行多个动作:
onView(...).perform(typeText("Hello"), click());```
如果你正在使用的视图位于内ScrollView(垂直或水平),考虑上述要求进行显示(如视图操作click()和typeText())用scrollTo()。这确保了视图在进行其他动作之前显示:
onView(...).perform(scrollTo(), click());```
注: scrollTo()不会有任何影响,如果已经显示视图,以便您可以在情况下,当视图显示,由于屏幕尺寸更大的安全使用(例如,当你的测试上更小和更大的屏幕分辨率下运行)。
见ViewActions由咖啡所提供的视图操作。
#####检查如果视图满足断言
断言可应用于与当前选择的视图check()的方法。最常用的断言是matches()断言,它使用一个ViewMatcher断言当前选择的视图的状态。
例如,要检查一个视图有文本“Hello!”
onView(...).check(matches(withText("Hello!")));```
开始使用一个简单的测试使用OnView由
在本实施例SimpleActivity中含有一个Button与一个TextView。当按钮被点击的内容TextView变化"Hello Espresso!"。以下是如何与咖啡测试:
1.单击按钮
第一步是寻找一个属性,有助于找到按钮。在该按钮SimpleActivity具有独特的R.id -完美!
onView(withId(R.id.button_simple))
现在进行点击:
onView(withId(R.id.button_simple)).perform(click());```
2.检查TextView现在包含“您好咖啡!”
在TextView与文本来验证具有独特R.id太:
onView(withId(R.id.text_simple))```
现在自行核实相关内容的文字:
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));```
> 找元素
我们现在需要找页面中对应的元素了!Espresso提供了一个onView()方法用来寻找UI上指定的元素,该方法定义如下:
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所对应的目标控件了。
再描述一遍这个流程以便更清晰:我现在要找一个R.id为指定id的控件,那么我就从我的这个id出发,先生成一个查找匹配条件:withId(id)。然后把这个条件传给onView()方法:onView(withId(id)),让onView()方法根据这个条件找到我们想要的那个控件!实际上这行代码也是很符合我们的正常思维,可以读作:
Find a view with Id of the specific id.```
实际上,Espresso提供了很多方法来让我们自定义我们的查找条件。比如我们可以通过withText()方法来寻找显示了指定文案的控件等等。具体支持的Matcher类型可以参考Espresso cheat sheet。
需要提醒大家一点的是,onView()方法在根据匹配条件进行查找时,它的目标是找到唯一的一个目标控件。如果我们制定的匹配条件有多个控件可以匹配(比如复用了layout的布局,或者显示相同文字的TextView等),该方法会抛出一个AmbiguousViewMatcherException异常,因此我们在构造匹配条件时,一定要确保能查找到的目标控件是唯一的。如果单一的匹配条件无法精确地匹配出来唯一的控件,我们可能还需要额外的匹配条件,此时可以用allOf()方法来进行复合匹配条件的构造:
onView(allOf(withId(id), withText(text)))```
以上代码可以查找ID为id同时显示的文字内容为text的控件。这里需要注意的是,为了保证自动化测试的效率,我们应尽可能减少匹配条件的数量。如果用一个匹配条件能够满足我们的需求,我们也就没有必要再用allOf()来构造复合匹配条件了。
操作元素
找到了目标元素,接下来我们该针对该元素做一些操作了!
Espresso提供了如下方法来对相应的元素做操作:
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。
onView(withText("Hello world!")).check(matches(isDisplayed()));```
检查"Hello world!"是否成功地显示在了屏幕上。
自己写的类:
package com.example.hante.mvp;
/**
- Created by handan on 2016/7/27.
*/
@RunWith(AndroidJUnit4.class)
@LargeTest
public class DemoTest {
private String mStringBeyond ;
@Rule
public ActivityTestRule<DemoActivity> demoActivityActivityTestRule =
new ActivityTestRule<DemoActivity>(DemoActivity.class){
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
}
@Override
protected void afterActivityLaunched() {
super.afterActivityLaunched();
}
@Override
protected void afterActivityFinished() {
super.afterActivityFinished();
}
};
@Before
public void initValidString(){
mStringBeyond = "Picasso";
}
@Test
public void changeText_demoActivity(){
onView(withId(R.id.input_edit)).perform(typeText(mStringBeyond),closeSoftKeyboard());
onView(withId(R.id.clickk)).perform(click()).check(matches(isDisplayed()));
onView(withId(R.id.input_edit)) // 找到id 传给onView
.check(matches(withText(mStringBeyond)));// 检查是否包含文本mStringBeyond
onView(allOf(withId(R.id.input_edit),withText("Hello")))//多个控件时,筛选出自己想要的
.perform(scrollTo(),click(),replaceText("HT"),closeSoftKeyboard())// 执行的操作
.check(matches(isDisplayed()));// 检查 匹配
onView(withId(R.id.input_edit)).check(matches(isClosed(Gravity.LEFT)));
onData(allOf(withId(R.id.clickk),withText(mStringBeyond))).perform(click());
}
}
>语法介绍
布局可能包含本身并不是唯一的某些视图(如触点可能具有相同的R.id的表中的重复呼叫按钮,包含相同的文本,并有视图层次结构中的相同属性的其他呼叫按钮)。
例如,在本次活动,与文字“7”的观点在多个行重复:
![有兄弟](https://google.github.io/android-testing-support-library/docs/images/hasSibling.png)
通常情况下,非唯一视图将与同时毗邻它的一些独特的标签配对(例如接触的一个名字旁边的呼叫按钮)。在这种情况下,你可以使用**hasSibling**匹配来缩小您的选择:
onView(allOf(withText("7"), hasSibling(withText("item: 0"))))
.perform(click());
``