65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 UiAutomator 进行自动 Android* 测试

2014年6月2日

CPOL

5分钟阅读

viewsIcon

22773

我想向您介绍一个用于自动测试 Android* 应用程序 UI 的强大工具。该工具名为 UiAutomator。

引言

我想向您介绍一个用于自动测试 Android* 应用程序 UI 的强大工具。该工具名为 UiAutomator。您可以在此处找到所有最新文档: https://developer.android.com.cn/tools/help/uiautomator/index.htmlhttps://developer.android.com.cn/tools/testing/testing_ui.html

UiAutomator 存在一些优点和缺点。

优点

  • 可用于具有不同分辨率的设备显示屏
  • 事件可以与 Android UI 控件关联。例如,点击一个文本为“确定”的按钮,而不是点击一个坐标位置(x=450,y=550)。
  • 可以重现复杂的用户操作序列
  • 始终执行相同的操作序列,使我们能够在不同设备上收集性能指标。
  • 无需更改任何 Java* 代码即可运行多次并在不同设备上运行
  • 可以使用设备上的硬件按钮

缺点

  • 难以用于 OpenGL* 和 HTML5 应用程序,因为这些应用程序没有 Android UI 组件。
  • 编写 JavaScript* 耗时

脚本开发

为了介绍 UiAutomator 的工作原理,我想展示一个简单的程序。该程序是标准的 Android Messaging 应用程序,用于向任何电话号码发送 SMS 消息。

以下是我们实现的操作的简要描述

  1. 查找并运行应用程序
  2. 创建并发送 SMS 消息

正如您所见,这非常简单。

测试准备

我们将使用 uiautomatorviewer 来分析 UI 界面。

uiautomatorviewer 在节点详细信息中显示所有 UI 组件的拆分屏幕截图,因此您可以看到它们的各种属性。通过属性,您可以找到所需的元素。

自定义开发环境

如果您使用 Eclipse*

  1. 在 Eclipse 中创建一个新的 Java 项目。我们将项目命名为:SendMessage
  2. 右键单击 **Project Explorer** 中的项目,然后单击 **Properties** 项
  3. 在 **Properties** 中,选择 **Java Build Path** 并添加所需的库
  • 单击 **Add Library** > **JUnit**,然后选择 **JUnit3** 以添加对 **JUnit** 的支持
  • 点击 **Add External JARs ...**
  • 在 **<android-sdk>/platforms/directory** 中,选择最新版本的 SDK。在此目录中,还选择文件:uiautomator.jarandroid.jar

如果您正在使用其他开发环境,请确保 android.jaruiautomator.jar 文件已添加到项目设置中。

创建脚本

在先前创建的新文件中创建一个项目,其中包含 Java 类。将其命名为 SendMessage。此类继承自 UiAutomatorTestCase 类,并使用快捷键 Ctrl + Shift + o(对于 Eclipse)添加所需的库。

创建三个函数来测试此应用程序

  1. 搜索并运行应用程序
  2. 发送 SMS 消息
  3. 退出到应用程序的主菜单

创建一个函数,我们将通过它来运行所有这些功能——一种主函数

public void test() {
    // Here will be called for all other functions
    }

查找并运行应用程序的函数

此函数很简单。我们按下 Home 按钮,一旦进入主窗口,打开菜单并查找带有应用程序图标。单击它即可启动应用程序。

private void findAndRunApp() throws UiObjectNotFoundException {
        // Go to main screen
        getUiDevice().pressHome();
        // Find menu button
        UiObject allAppsButton = new UiObject(new UiSelector()
        .description("Apps"));
        // Click on menu button and wait new window
        allAppsButton.clickAndWaitForNewWindow();
        // Find App tab
        UiObject appsTab = new UiObject(new UiSelector()
        .text("Apps"));
        // Click on app tab
        appsTab.click();
        // Find scroll object (menu scroll)
        UiScrollable appViews = new UiScrollable(new UiSelector()
        .scrollable(true));
        // Set the swiping mode to horizontal (the default is vertical)
        appViews.setAsHorizontalList();
        // Find Messaging application
        UiObject settingsApp = appViews.getChildByText(new UiSelector()
        .className("android.widget.TextView"), "Messaging");
        // Open Messaging application
        settingsApp.clickAndWaitForNewWindow();
        
        // Validate that the package name is the expected one
        UiObject settingsValidation = new UiObject(new UiSelector()
        .packageName("com.android.mms"));
        assertTrue("Unable to detect Messaging",
                settingsValidation.exists());
    }

所有类名、按钮上的文本等均来自 uiautomatorviewer。

发送 SMS 消息

此函数查找并按下编写新应用程序的按钮,输入要发送短信的电话号码,然后按下发送按钮。电话号码和文本通过函数参数传递

private void sendMessage(String toNumber, String text) throws UiObjectNotFoundException {
        // Find and click New message button
        UiObject newMessageButton = new UiObject(new UiSelector()
        .className("android.widget.TextView").description("New message"));
        newMessageButton.clickAndWaitForNewWindow();
        
        // Find to box and enter the number into it
        UiObject toBox = new UiObject(new UiSelector()
        .className("android.widget.MultiAutoCompleteTextView").instance(0));
        toBox.setText(toNumber);
        // Find text box and enter the message into it
        UiObject textBox = new UiObject(new UiSelector()
        .className("android.widget.EditText").instance(0));
        textBox.setText(text);
        
        // Find send button and send message
        UiObject sendButton = new UiObject(new UiSelector()
        .className("android.widget.ImageButton").description("Send"));
        sendButton.click();
    }

电话号码和短信显示字段没有任何特殊功能,因为既没有文本也没有对这些字段的描述。因此,我可以通过在此实例中使用接口层次结构中的序数位置的元素来找到它们。

为了能够将参数传递给脚本,我们可以指定要发送消息的号码以及文本消息。`test()` 函数初始化默认设置,如果通过命令行发送了任何参数消息,则会替换默认设置。

// Default parameters
        String toNumber = "123456"; 
        String text = "Test message";
        
        String toParam = getParams().getString("to");
        String textParam = getParams().getString("text");
if (toParam != null) {
// Remove spaces
            toNumber = toParam.trim();
        }
        if (textParam != null) {
            text = textParam.trim();
        }

因此,我们将能够通过脚本使用小键 -e、参数名称和值从命令行传递参数。例如,我的应用程序发送号码“777777”:-e 777777

存在一些陷阱。例如,此应用程序不识别某些字符并会失败。无法仅传输包含某些字符的文本,它不会识别它们并会失败。其中一些包括:空格、&、<、>、(,)、"、',以及一些 Unicode 字符。我会在应用它们来编写脚本字符串时替换这些字符,例如空格行:blogspaceblog。因此,当脚本启动 UiAutomator 时,我们使用一个脚本来处理我们的输入参数。我们添加 `test()` 函数,在该函数中我们检查是否有选项,解析参数,并将它们替换为实际字符。以下是一个样本代码,并演示了我们之前插入的内容。

if (toParam != null) {
            toParam = toParam.replace("blogspaceblog", " ");
            toParam = toParam.replace("blogamperblog", "&");
            toParam = toParam.replace("bloglessblog", "<");
            toParam = toParam.replace("blogmoreblog", ">");
            toParam = toParam.replace("blogopenbktblog", "(");
            toParam = toParam.replace("blogclosebktblog", ")");
            toParam = toParam.replace("blogonequoteblog", "'");
            toParam = toParam.replace("blogtwicequoteblog", "\"");
            toNumber = toParam.trim();
        }
        if (textParam != null) {
            textParam = textParam.replace("blogspaceblog", " ");
            textParam = textParam.replace("blogamperblog", "&");
            textParam = textParam.replace("bloglessblog", "<");
            textParam = textParam.replace("blogmoreblog", ">");
            textParam = textParam.replace("blogopenbktblog", "(");
            textParam = textParam.replace("blogclosebktblog", ")");
            textParam = textParam.replace("blogonequoteblog", "'");
            textParam = textParam.replace("blogtwicequoteblog", "\"");
            text = textParam.trim();
        }

退出到应用程序的主菜单

此函数是我们实现的所有函数中最简单的。我只需在一个循环中按下后退按钮,直到显示创建新消息的按钮。

private void exitToMainWindow() {
        // Find New message button
        UiObject newMessageButton = new UiObject(new UiSelector()
        .className("android.widget.TextView").description("New message"));
        
        // Press back button while new message button doesn't exist
        while(!newMessageButton.exists()) {
            getUiDevice().pressBack();
        }
    }

源代码

这是我们的代码

package blog.send.message;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiScrollable;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;

public class SendMessage extends UiAutomatorTestCase {
    public void test() throws UiObjectNotFoundException {
        // Default parameters
        String toNumber = "123456"; 
        String text = "Test message";
        
        String toParam = getParams().getString("to");
        String textParam = getParams().getString("text");
        if (toParam != null) {
            toParam = toParam.replace("blogspaceblog", " ");
            toParam = toParam.replace("blogamperblog", "&");
            toParam = toParam.replace("bloglessblog", "<");
            toParam = toParam.replace("blogmoreblog", ">");
            toParam = toParam.replace("blogopenbktblog", "(");
            toParam = toParam.replace("blogclosebktblog", ")");
            toParam = toParam.replace("blogonequoteblog", "'");
            toParam = toParam.replace("blogtwicequoteblog", "\"");
            toNumber = toParam.trim();
        }
        if (textParam != null) {
            textParam = textParam.replace("blogspaceblog", " ");
            textParam = textParam.replace("blogamperblog", "&");
            textParam = textParam.replace("bloglessblog", "<");
            textParam = textParam.replace("blogmoreblog", ">");
            textParam = textParam.replace("blogopenbktblog", "(");
            textParam = textParam.replace("blogclosebktblog", ")");
            textParam = textParam.replace("blogonequoteblog", "'");
            textParam = textParam.replace("blogtwicequoteblog", "\"");
            text = textParam.trim();
        }
        findAndRunApp();
            sendMessage(toNumber, text);
            exitToMainWindow();
    }
    // Here will be called for all other functions
    private void findAndRunApp() throws UiObjectNotFoundException {
        // Go to main screen
        getUiDevice().pressHome();
        // Find menu button
        UiObject allAppsButton = new UiObject(new UiSelector()
        .description("Apps"));
        // Click on menu button and wait new window
        allAppsButton.clickAndWaitForNewWindow();
        // Find App tab
        UiObject appsTab = new UiObject(new UiSelector()
        .text("Apps"));
        // Click on app tab
        appsTab.click();
        // Find scroll object (menu scroll)
        UiScrollable appViews = new UiScrollable(new UiSelector()
        .scrollable(true));
        // Set the swiping mode to horizontal (the default is vertical)
        appViews.setAsHorizontalList();
        // Find Messaging application
        UiObject settingsApp = appViews.getChildByText(new UiSelector()
        .className("android.widget.TextView"), "Messaging");
        // Open Messaging application
        settingsApp.clickAndWaitForNewWindow();
        
        // Validate that the package name is the expected one
        UiObject settingsValidation = new UiObject(new UiSelector()
        .packageName("com.android.mms"));
        assertTrue("Unable to detect Messaging",
                settingsValidation.exists());
    }
    
    private void sendMessage(String toNumber, String text) throws UiObjectNotFoundException {
        // Find and click New message button
        UiObject newMessageButton = new UiObject(new UiSelector()
        .className("android.widget.TextView").description("New message"));
        newMessageButton.clickAndWaitForNewWindow();
        
        // Find to box and enter the number into it
        UiObject toBox = new UiObject(new UiSelector()
        .className("android.widget.MultiAutoCompleteTextView").instance(0));
        toBox.setText(toNumber);
        // Find text box and enter the message into it
        UiObject textBox = new UiObject(new UiSelector()
        .className("android.widget.EditText").instance(0));
        textBox.setText(text);
        
        // Find send button and send message
        UiObject sendButton = new UiObject(new UiSelector()
        .className("android.widget.ImageButton").description("Send"));
        sendButton.click();
    }
    
    private void exitToMainWindow() {
        // Find New message button
        UiObject newMessageButton = new UiObject(new UiSelector()
        .className("android.widget.TextView").description("New message"));
        
        // Press back button while new message button doesn't exist
        while(!newMessageButton.exists()) {
            getUiDevice().pressBack();
            sleep(500);
        }
    }
}

编译并运行 UiAutomator 测试

  1. 要生成测试程序集配置文件,请从命令行运行以下命令
    <android-sdk>/tools/android create uitest-project -n <name> -t <target-id> -p <path>
    其中 <name> 是为测试 UiAutomator 创建的项目名称(在本例中为:SendMessage),<target-id> 是设备和 Android API 级别的选择(您可以通过命令 (<android-sdk>/tools/android list targets) 获取已安装设备的列表,以及 <path> 是包含项目的目录路径。
  2. 您必须导出环境变量 ANDROID_HOME
    • 在 Windows* 上:
      set ANDROID_HOME=<path_to_your_sdk>
    • 在 UNIX* 上
      export ANDROID_HOME=<path_to_your_sdk>
  3. 转到项目文件 build.xml 所在的目录,该文件已在步骤 1 中生成,然后运行命令
    ant build
  4. 使用 adb push 将编译后的 JAR 文件复制到设备
    adb push <path_to_output_jar> /data/local/tmp/
    在本例中,它是
    adb push <project_dir>/bin/SendMessage.jar /data/local/tmp/
  5. 并运行脚本
    adb shell uiautomator runtest /data/local/tmp/SendMessage.jar –c blog.send.message.SendMessage

相关文章与资源

要了解更多关于安卓开发者的英特尔工具,请访问英特尔® 安卓开发者专区

© . All rights reserved.