NOTE : Espresso UI测试

官方文档

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());







  ``
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • Instrumentation介绍 Instrumentation是个什么东西? Instrumentation测...
    打不死的小强qz阅读 7,776评论 2 39
  • 什么是单元测试 在计算机编程中,单元测试(Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最...
    HelloCsl阅读 10,946评论 1 46
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,900评论 25 707
  • 20170522,难忘的一天,今天是我再次重生的日子,感谢石老师,亲爱的女神老师,运用了她的特殊功能,唤醒了我的身...
    铭玮阅读 630评论 0 5
  • 梅,是我的梅一颗娇弱的玉 花蕊,是你的手指我的心像一枝蜡烛,烧的很静任你轻拔 梦,不醒——就是天堂——或是人世间世...
    泪花香阅读 173评论 0 0