Android修炼系列(40),Espresso UI 自动化测试

1,866 阅读5分钟

Espresso 测试框架(Android 4.0.1,API 级别 14 或更高版本)提供了 Android UI 测试的 API,用来模拟用户与应用的 视图交互。本文从实践的角度,讲下 Espresso 的使用,希望也能为大家避坑。关于更过 Espresso 的介绍,可点击 官网传送门

本 demo androidTest 自动化效果:

blog_zuo.gif

Espresso

在集成 Espresso 的时候,可能会遇到一些坑,主要是些依赖的版本冲突问题。具体版本没有要求,如果编译不通过,可参考下我的 demo:

gradle 文件中我引用了这几个 androidTest 库:

    androidTestImplementation "androidx.test.ext:junit:1.1.4-alpha06"
    androidTestImplementation "androidx.test:runner:1.5.0-alpha03"
    androidTestImplementation "androidx.test:core:1.4.1-alpha06"
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'

如果使用 kotlin,还需要引入:

    androidTestImplementation "androidx.test.ext:junit-ktx:1.1.4-alpha06"
    androidTestImplementation "androidx.test:core-ktx:1.4.1-alpha06"
    androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10"

注意 espressoVersion 的当前最新版 3.5.0-alpha06 测着有问题,ViewMatchers 时会报错,可以使用 3.5.0-alpha05

    java.lang.IncompatibleClassChangeError: androidtest class 'org.hamcrest.stringdescription' does not implement interface 'java.lang.iterable' in call to 'java.util.iterator java.lang.iterable.iterator()'

API 组件

Espresso 的主要组件包括:

  • Espresso - 用于与视图交互(通过 onView() 和 onData())的入口点。此外,还公开不一定与任何视图相关联的 API,如 pressBack()
  • ViewMatchers - 实现 Matcher<? super View> 接口的对象的集合。我们可以将其中一个或多个对象传递给 onView() 方法,用来查找特定视图。
  • ViewActions - 可以传递给 ViewInteraction.perform() 方法的 ViewAction 对象的集合,例如 click()
  • ViewAssertions - 可以通过 ViewInteraction.check() 方法传递的 ViewAssertion 对象的集合。在大多数情况下,我们可以使用 matches 断言当前选中视图的状态。

下面来写个示例:

image.png

api 都很小巧,体会下,应该就可以上手了。

三板斧,查找视图 -> 对视图操作 -> 对视图断言。

简单测试

EspressoUiActivity 类中包含一个 Button 和一个 TextView。点击该按钮后,TextView 的内容会变为 "espresso"。

image.png

我们在 src/androidTest/java/ 路径下编写测试类 EspressoUiTest,注意 activityScenarioRule 不能忘:

image.png

好了,执行用例,发现用例通过:

image.png

AdapterView测试

AdapterView 可从适配器动态加载数据。最常见的 AdapterView 是 ListView。和 LinearLayout 不同的是,其有时只能将一部分 AdapterView 子视图加载到当前视图层次结构中。使用简单的 onView() 将找不到当前未加载的视图。

Espresso 为此提供一个单独的 api onData(),该 api 能够先加载相关适配器数据,并在对其或其任何子级执行操作之前使其处于聚焦状态。

来个示例:

EspressoUiActivity 类中包含一个 ListView 和一个 TextView。点击该item后,TextView 的内容会变为 "item$position"。

image.png

首先我们在测试类 EspressoUiTes 中使用 onData,可以模拟用户操作:

image.png

RecyclerView 测试

关于 RecyclerView 的 UI 测试,我们需要额外引用依赖库:

androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.0-alpha05'

来个示例吧,跟 AdapterView 使用类似:

EspressoUiActivity 类中包含一个 RecyclerView,代码见下。

image.png

这是适配器类 CustomAdapter 部分代码,就是取 middle' item 赋值 “middle”,其他 item 赋值 “item$position”:

image.png

我们在测试类 EspressoUiTest 使用 RecyclerViewActions 提供的接口,可以来模拟用户操作:

image.png

不复杂吧,不止如此,RecyclerViewActions 还提供了 api:

  • scrollTo() - 滚动到匹配的视图。
  • scrollToHolder() - 滚动到匹配的数据视图持有者。
  • scrollToPosition() - 滚动到特定位置。
  • actionOnHolderItem() - 对匹配的视图持有者执行视图操作。
  • actionOnItem() - 对匹配的视图执行视图操作。
  • actionOnItemAtPosition() - 在特定位置对视图执行视图操作。

我们再来看个 scrollToHolder() 的示例,可以自定义个匹配器,在 matchesSafely 编写匹配条件(发散下,我们完全可以通过 Holder 做更多的匹配工作,甚至与外部传入的其他 Matcher 共同作用):

image.png

自定义Matcher

EspressoUiActivity 类中包含一个 EditTest,其 hint 属性为 “please input”。

现在编写一个 withHint 方法,将 BoundedMatcher 用作基匹配器,用来匹配 EditTest 的 hint。

image.png

现在就可以使用自定义的 withHint 接口了,即可以接收 String 类型,也可以接收 Matcher<String> 类型:

image.png

当然使用 TypeSafeMatcher 实现也没问题,大家灵活运用吧:

image.png

isDisplayed 和 doesNotExist()

isDisplayed 表示 view 被展示出来了,与 not(isDisplayed) 对应,使用起来很简单。

EspressoUiActivity 类中包含一个 Button,处于 Visible 状态,点击后置于 Gone / Invisible 状态。

image.png

我们在测试类 EspressoUiTest 中,可以使用 isDisplayed 检查:

image.png

doesNotExist 表示视图不见了,即相应视图从视图层次结构中消失了,不是隐藏。下面来个例子:

EspressoUiActivity 类中包含一个 Button,处于 Visible 状态,点击后从父布局中移除。

image.png

我们在测试类 EspressoUiTest 中,可以使用 doesNotExist 检查:

image.png

Espresso-Intents

Espresso-Intents 是 Espresso 的扩展程序,它能够帮助我们 从下面2个方面来验证 Intent

  • 验证传出的 Intent 携带的数据
  • 模拟 ActivityResult

使用 Espresso-Intents 需要引入依赖库:

androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.0-alpha05'

下面分别介绍下,怎么编写测试用例:

send Intent

EspressoUiActivity 类中包含一个 Button。点击该按钮后,会跳转到另一个 Activity 界面。

image.png

我们可以在测试类 EspressoUiTest 中,使用 Intents.intended 接口:

image.png

ActivityResult

EspressoUiActivity 类中包含一个 Button 和 一个 TextView。点击 Button 后,会跳转到另一个界面 EspressoUiActivity2,返回时, TextView 的内容变为 ActivityResult 携带的结果。

image.png

我们在测试类 EspressoUiTest 中,可以使用 Intents.intending 接口,同样别忘了 Intents.init()

image.png

有一点要注意,使用了 ActivityResultLauncher,就不能直接 startActivity 了,可以使用 startForResult.launch。

多窗口

当一个界面存在多个窗口时,Espresso 可能会迷惑,不知道哪个是要交互的目标窗口,这个时候,就需要我们手动指定,例如:AutoCompleteTextView 这类自动补全的 带下拉菜单的 View。

来个示例吧:

EspressoUiActivity 类中包含一个 AutoCompleteTextView,当输入 “Red” 时,下拉菜单会补全为 “Rea Sea” 可供点击。

image.png

我们可以在测试类 EspressoUiTest 中,我们需要 inRoot(withDecorView(..)) 指定窗口:

image.png

注意,并非所以窗口 Espresso 都识别不了。像 AlterDialog 这类,使用起来跟普通 view 一样简单。

来个示例吧:

EspressoUiActivity 类中包含一个 Button 和 一个 TextView,当点击 Buttton 后。会弹出 AlterDialog 展示 item 列表供点击,点击会在 TextView 显示。

image.png

我们可以在测试类 EspressoUiTest 中,直接使用 onData api 即可:

image.png

好了,本节就到这里了,代码我都上传 gitHub 了。

本节完。

参考资料