自动化测试 | UI Automator 进阶指南
UI Automator 相关介绍:
- 跨应用的用户界面自动化测试
 - 包含在 AndroidX Test(https://developer.android.com/training/testing) 中
 - 支持的 Android 系统:>= Android 4.3 (API level 18)
 - 基于 instrumentation,依赖于 AndroidJUnitRunner 测试运行器
 
设置 UI Automator(Set up UI Automator)
在编写测试代码前,先确保以下两个配置:
1、测试代码存放位置
2、项目依赖(https://developer.android.com/training/testing/set-up-project)
(1) 添加 Gradle 依赖(Add Gradle dependencies)
- app 目录下的 build.gradle 添加:
 
allprojects {
    repositories {
        jcenter()
        google()
    }
}
- dependencies 添加需要的 AndroidX Test Package, 比如:
 
dependencies {
    ...
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0'
}
- 如果测试代码需要基于 junit 的类,比如 Assert 和 TestSuiteLoader,在 android 区块中添加(只需要添加需要用到的 library:https://developer.android.com/training/testing/set-up-project#junit-based-libs):
 
android {
    ...
    // Gradle automatically adds 'android.test.runner' as a dependency.
    useLibrary 'android.test.runner'
    useLibrary 'android.test.base'
    useLibrary 'android.test.mock'
}
(2) 添加 manifest 声明(Add manifest declarations)
此步骤可选,具体请看 https://developer.android.com/training/testing/set-up-project#add-manifest-declarations
当前面的配置完成后,进行其他配置:
app下的build.gralde:
dependencies {
    ...
    androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
当所有配置都完成后,进行被测应用的 UI 组件分析,确保能被识别以及接入控制。
检查设备上的用户界面(Inspect the UI on a device)
uiautomatorviewer:
(1)	启动手机上的被测应用
(2)	手机连接电脑
(3)	打开 Terminal, 进入目录 <android-sdk>/tools/
(4)	运行:uiautomatorviewer
查看应用的用户界面属性:
(1)	点击左上角 "Device Screenshot" 按钮
(2)	左边是 UI 组件,右下半部分是属性,右上半部分是布局层级
(3)	可选功能:点击右上角 "Toggle NAF Nodes" 按钮(黄色三角形,内有感叹号),查看无法被识别/访问的UI组件。---这个功能我都没搞懂怎么用,点击后貌似没效果
确保 activity 可访问(Ensure your activity is accessible)
Android 原生元素具有更好的访问性,利于测试代码的编写,无需额外的支持
如果是自定义 UI 元素,需要(1)创建一个继承自 ExploreByTouchHelper 的实体类(2)通过调用 setAccessibilityDelegate() 将新创建的类的实例和特定的自定义 UI 元素相关联
给自定义视图元素添加无障碍功能的其他参考资料:https://developer.android.com/guide/topics/ui/accessibility/custom-views.html
学习资料 for 提高 Android 的无障碍性/可访问性:https://developer.android.com/guide/topics/ui/accessibility/apps.html
创建一个 UI Automator 测试类(Create a UI Automator test class)
UI Automator 测试类的写法和 JUnit 4 测试类的写法是一样的。
JUnit 4 测试类的学习资料:https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests.html#build
在测试类开头添加注解:@RunWith(AndroidJUnit4.class)
同时,明确 AndroidX Test 中的 AndroidJUnitRunner 类为默认的测试运行器。这个步骤的详细描述:https://developer.android.com/training/testing/ui-testing/uiautomator-testing.html#run
在 UI Automator 测试类中执行以下编程模型:
- 获取一个 UiDevice 对象去接入测试设备,调用 getInstance() 方法,传入 Instrumentation 对象作为参数。
 - 通过 UiObject 对象调用 findObject() 方法接入显示在设备上的 UI 组件(例如,当前手机屏幕显示的用户界面)。
 - 通过调用 UiObject 方法在 UI 组件上模拟一个交互的动作。例如,调用 performMultiPointerGesture() 方法模拟多指触控,调用 setText() 方法编辑文本框。当测试包含多个 UI 组件或者更加复杂的操作序列时,在第二步和第三步中可重复调用各种 API.
 - 当执行完这些用户交互的动作后,检查返回的结果是否符合预期。
这些步骤在以下章节会讲的更加详细。 
访问用户界面组件 (Access UI components)
UiDevice: 接入和控制设备状态的首要方法,可执行设备级别的行为,例如改变屏幕旋转方向、按下硬件按钮、以及点击 home 和 menu 键。
从设备的主屏幕开始测试是一个好的实践。在主屏幕(或者其他你在设备上选定的开始位置),可以调用 UI Automator API 提供的方法和指定的 UI 元素进行交互。
以下代码片段展示了如何获取一个 UiDevice 的实例以及模拟按下 home 键的操作:
import org.junit.Before;
import androidx.test.runner.AndroidJUnit4;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.Until;
...
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18)
public class ChangeTextBehaviorTest {
    private static final String BASIC_SAMPLE_PACKAGE
            = "com.example.android.testing.uiautomator.BasicSample";
    private static final int LAUNCH_TIMEOUT = 5000;
    private static final String STRING_TO_BE_TYPED = "UiAutomator";
    private UiDevice device;
    @Before
    public void startMainActivityFromHomeScreen() {
        // Initialize UiDevice instance
        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
        // Start from the home screen
        device.pressHome();
        // Wait for launcher
        final String launcherPackage = device.getLauncherPackageName();
        assertThat(launcherPackage, notNullValue());
        device.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
                LAUNCH_TIMEOUT);
        // Launch the app
        Context context = ApplicationProvider.getApplicationContext();
        final Intent intent = context.getPackageManager()
                .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
        // Clear out any previous instances
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
        context.startActivity(intent);
        // Wait for the app to appear
        device.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
                LAUNCH_TIMEOUT);
    }
}
示例代码中的声明:@SdkSuppress(minSdkVersion = 18), 帮助确定测试只运行在 Android4.3(API level 18)或更高级别的设备上。(UI Automator 框架要求的)
- findObject()
 - UiObject
 
UiObject cancelButton = device.findObject(new UiSelector()
        .text("Cancel")
        .className("android.widget.Button"));
UiObject okButton = device.findObject(new UiSelector()
        .text("OK")
        .className("android.widget.Button"));
// Simulate a user-click on the OK button, if found.
if(okButton.exists() && okButton.isEnabled()) {
    okButton.click();
}
指定一个选择器(Specify a selector)
UiSelector 类:在当前显示的用户界面中查询一个特定的元素。
- childSelector()
 - UiAutomatorObjectNotFoundException
 
UiObject appItem = device.findObject(new UiSelector()
        .className("android.widget.ListView")
        .instance(0)
        .childSelector(new UiSelector()
        .text("Apps")));
tips:
- 如果在一个页面上找到一个以上的相同元素,自动返回第一个匹配的元素作为目标 UiObject.
 - 可以通过整合多个属性来缩小搜索范围。
 - 如果没有找到目标元素,抛出 UiAutomatorObjectNotFoundException 异常。
 - 可以使用 childSelector() 方法缩小多个 UiSelector 实例范围。
 - 如果有 Resource ID, 用这个代替 text 和 content-descripter.
 - text 元素比较脆弱,有多种原因可能导致测试失败。(比如:多语言)
在选择区域中去明确一个对象状态是非常有用的。比如:选择一个已选中的列表以进行取消选中状态,调用 checked() 方法,将参数设为 "true". 
执行动作(Perform actions)
当获取 UiObject 对象后,可以调用 UiObject 类中的方法在其上执行相应操作:
- click(): 点击
 - dragTo(): 拖动
 - setText(): 设置文本
 - clearTextField(): 清空文本
 - swipeUp(): 向上滑动
 - swipeDown(): 向下滑动
 - swipeLeft(): 向左滑动
 - swipeRight(): 向右滑动
 
通过 getContext() 方法获取到 Context 后,可以进行发送 Intent 或者启动 Activity 的操作。
public void setUp() {
    ...
    // Launch a simple calculator app
    Context context = getInstrumentation().getContext();
    Intent intent = context.getPackageManager()
            .getLaunchIntentForPackage(CALC_PACKAGE);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
    // Clear out any previous instances
    context.startActivity(intent);
    device.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}
在集合上执行动作(Perform actions on collections)
- UiCollection 类:在一个 item 的集合上模拟用户操作(例如,歌曲列表或者邮件列表)。
如何创建一个 UiCollection 对象:明确一个搜索UI容器或者其他子 UI 元素集合的 UiSelector. 例如,包含子 UI 元素的 layout 视图。 
UiCollection videos = new UiCollection(new UiSelector()
        .className("android.widget.FrameLayout"));
// Retrieve the number of videos in this collection:
int count = videos.getChildCount(new UiSelector()
        .className("android.widget.LinearLayout"));
// Find a specific video and simulate a user-click on it
UiObject video = videos.getChildByText(new UiSelector()
        .className("android.widget.LinearLayout"), "Cute Baby Laughing");
video.click();
// Simulate selecting a checkbox that is associated with the video
UiObject checkBox = video.getChild(new UiSelector()
        .className("android.widget.Checkbox"));
if(!checkBox.isSelected()) checkbox.click();
在可滚动视图中执行动作(Perform actions on scrollable views)
- UiScrollable 类:在手机屏幕上模拟垂直或者水平的滚动操作。这个类适用于当需要找到屏幕外的 UI 元素时,可以通过滚动操作将这个 UI 元素带到屏幕内。
 
UiScrollable settingsItem = new UiScrollable(new UiSelector()
        .className("android.widget.ListView"));
UiObject about = settingsItem.getChildByText(new UiSelector()
        .className("android.widget.LinearLayout"), "About tablet");
about.click();
验证结果(Verify results)
InstrumentationTestCase 继承自 TestCase,可以使用标准的 JUnit Assert 方法进行结果验证。
以下代码片段展示了如何验证计算器加法:
private static final String CALC_PACKAGE = "com.myexample.calc";
public void testTwoPlusThreeEqualsFive() {
    // Enter an equation: 2 + 3 = ?
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("two")).click();
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("plus")).click();
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("three")).click();
    device.findObject(new UiSelector()
            .packageName(CALC_PACKAGE).resourceId("equals")).click();
    // Verify the result = 5
    UiObject result = device.findObject(By.res(CALC_PACKAGE, "result"));
    assertEquals("5", result.getText());
}
在设备或虚拟机上运行 UI Automator 测试用例(Run UI Automator tests on a device or emulator)
可以通过 Android Studio 或者命令行运行 UI Automator tests. 确保项目的默认 instrumentation runner 是 AndroidJUnitRunner.
参考资料(Additional resources)
Samples:
https://github.com/googlesamples/android-testing/tree/master/ui/uiautomator/BasicSample  基础的UI Automator 示例代码
Codelabs:
https://codelabs.developers.google.com/codelabs/android-testing/index.html
欢迎关注微信公众号"测试开发Stack"
自动化测试 | UI Automator 进阶指南的更多相关文章
- 自动化测试 | UI Automator 入门指南
		
自动化测试的定义,这里先引用一段维基百科的定义: 在软件测试中,测试自动化(英语:Test automation)是一种测试方法,使用特定的软件,去控制测试流程,并比较实际的结果与预期结果之间的差异. ...
 - iOS进阶指南试读之UI篇
		
iOS进阶指南试读之UI篇 UI篇 UI是一个iOS开发工程师的基本功.怎么说?UI本质上就是你调用苹果提供给你的API来完成设计师的设计.所以,想提升UI的功力也很简单,没事就看看UIKit里的各个 ...
 - 【读书笔记】读《高性能网站建设指南》及《高性能网站建设进阶指南:Web开发者性能优化最佳实践》
		
这两本书就一块儿搞了,大多数已经理解,简单做个标记.主要对自己不太了解的地方,做一些记录. 一.读<高性能网站建设指南> 0> 黄金性能法则:只有10%~20%的最终用户响应时间 ...
 - Weex入门与进阶指南
		
Weex入门与进阶指南 标签: WeexiOSNative 2016-07-08 18:22 59586人阅读 评论(8) 收藏 举报 本文章已收录于: iOS知识库 分类: iOS(87) 职 ...
 - UI Automator 介绍
		
简介 Android 4.3发布的时候包含了一种新的测试工具–uiautomator,uiautomator是用来做UI测试的.也就是普通的手工测试,点击每个控件元素 看看输出的结果是否符合预期.比如 ...
 - Appium自动化(10) - appium高级元素定位方式之 UI Automator API 的详解
		
如果你还想从头学起Appium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1693896.html 前言 前面介绍过根据id,clas ...
 - UI Automator Viewer获取手机镜像时报错
		
使用UI Automator Viewer获取手机镜像时报错,具体信息如下: Error while obtaining UI hierarchy XML file: com.android.ddml ...
 - HTML5游戏开发进阶指南(亚马逊5星畅销书,教你用HTML5和JavaScript构建游戏!)
		
HTML5游戏开发进阶指南(亚马逊星畅销书,教你用HTML5和JavaScript构建游戏!) [印]香卡(Shankar,A.R.)著 谢光磊译 ISBN 978-7-121-21226-0 201 ...
 - HTML5游戏开发进阶指南
		
<HTML5游戏开发进阶指南> 基本信息 作者: (印)香卡(Shankar,A.R.) 译者: 谢光磊 出版社:电子工业出版社 ISBN:9787121212260 上架时间:20 ...
 
随机推荐
- C#中get和set属性的作用
			
c#在定义类时,通常要把类中声明的对象封装起来,使得外界不能访问这个属性.上述代码中如果去掉set部分,则外界只能读取name的值,如果去掉get部分,则只能给name赋值.这样就可以控制外界对私有属 ...
 - vi/vim 三种模式的操作
			
来源:http://www.runoob.com/linux/linux-vim.html ps:刚刚进入vi/vim 是命令模式 一.命令模式 i 切换到输入模式,以输入字符. x 删除当前光标所在 ...
 - spring深入学习(四)-----spring aop
			
AOP概述 aop其实就是面向切面编程,举个例子,比如项目中有n个方法是对外提供http服务的,那么如果我需要对这些http服务进行响应时间的监控,按照传统的方式就是每个方法中添加相应的逻辑,但是这些 ...
 - 1004 Counting Leaves 对于树的存储方式的回顾
			
一种新的不使用左右子树递归进行树高计算的方法,使用层次遍历 树的存储方式: 1.本题提供的一种思路: 使用(邻接表的思想)二维数组(vector[n])表示树,横坐标表示 父节点,每一行表示孩子. 能 ...
 - 解决jenkins shell执行sonar-scanner提示命令存在的问题
			
通过jenkins的以下三个方式去执行sonar-scanner,抛如下错误. Send files or execute commands over SSH before the build sta ...
 - Unity3D编辑器扩展(四)——扩展自己的组件
			
前面已经写了三篇: Unity3D编辑器扩展(一)——定义自己的菜单按钮 Unity3D编辑器扩展(二)——定义自己的窗口 Unity3D编辑器扩展(三)——使用GUI绘制窗口 今天写第四篇,扩展自己 ...
 - Python数据结构之单链表
			
Python数据结构之单链表 单链表有后继结点,无前继结点. 以下实现: 创建单链表 打印单链表 获取单链表的长度 判断单链表是否为空 在单链表后插入数据 获取单链表指定位置的数据 获取单链表指定元素 ...
 - 《Linux就该这么学》
			
参加了第19期课程的培训,感谢刘老师的辛苦付出,课程讲的很好,真心推荐老刘的这本书真是<Linux就该这么学>!!! 本书是由全国多名红帽架构师(RHCA)基于最新Linux系统共同编写的 ...
 - 入门SQL操作
			
结构化查询语言:SQL:Structured Query Language 分类: 针对的操作对象不同.分成不同的语言: 1.数据操作(管理)语言( DML) 查询:获得数据.(DQL) 管理:增加. ...
 - 11-Python操作excel
			
1.python操作excel需要用到的库 python操作excel主要用到xlrd和xlwt这两个库,即xlrd是读excel,xlwt是写excel的库.可以直接pip安装这两个库,pip in ...