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 测试类中执行以下编程模型:

  1. 获取一个 UiDevice 对象去接入测试设备,调用 getInstance() 方法,传入 Instrumentation 对象作为参数。
  2. 通过 UiObject 对象调用 findObject() 方法接入显示在设备上的 UI 组件(例如,当前手机屏幕显示的用户界面)。
  3. 通过调用 UiObject 方法在 UI 组件上模拟一个交互的动作。例如,调用 performMultiPointerGesture() 方法模拟多指触控,调用 setText() 方法编辑文本框。当测试包含多个 UI 组件或者更加复杂的操作序列时,在第二步和第三步中可重复调用各种 API.
  4. 当执行完这些用户交互的动作后,检查返回的结果是否符合预期。
    这些步骤在以下章节会讲的更加详细。

访问用户界面组件 (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"

04-20 13:06