Android自动测试框架Espresso

不错的官方文档翻译(含作者见解)
这篇文章翻译简直太棒了,本文的内容都来自对该文章所记录的笔记。
Android自动化测试--Espresso框架使用
Android官网,_科学上网也访问不了跪求原因

如何使用Espresso

  1. 在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

自定义Matcher

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

推荐阅读更多精彩内容