我将为大家介绍一款出色的工具,它能够自动测试 Android* 应用的 UI。 这款工具称为 UiAutomator。 您可以从下链接获得最新的文档: http://developer.android.com/tools/help/uiautomator/index.html 和http://developer.android.com/tools/testing/testing_ui.html
UiAutomator 具备一些优势和缺点 。
优势:
- 可用于使用不同分辨率的设备显示器上
- 事件能够链接至 Android UI 控制。 例如,点击带有文本 “Ok” 的按钮,而无需点击坐标位置(x=450,y=550)。
- 能够复制复杂的用户操作序列
- 总是执行相同的动作序列,从而能够帮助我们在不同的设备上收集性能标准。
- 能够不改变任何 Java* 代码而多次运行并在不同的设备上运行
- 能够使用设备上的硬件按钮
缺点:
- 无法配合 OpenGL* 和 HTML5 应用使用,因为这些应用没有 Android UI 组件。
- 编写 JavaScript* 非常耗时
脚本部署
为了对如何使用 UiAutomator 进行介绍,我将展示一个简单的程序。 该程序是标准的 Android 信息应用,能够向任何一个手机号码发送短信。
以下简要介绍了我们实施的操作:
- 查找并运行应用程序
- 创建和发送短信
如您所见,这非常简单。
测试准备
为了分析 UI 界面,我们将使用 uiautomatorviewer。
uiautomatorviewer 展示了 Node Detail 中所有 UI 组件的截屏,因此您能够看到不同的属性。 从属性中,您可以找到目标元素。
定制开发环境
如果您使用的是 Eclipse*:
- 在 Eclipse 中创建一个新的 Java 项目。 我们将该项目称为: SendMessage
- 在 Project Explorer 中右击您的项目并点击Properties 项
- 在 Properties 中选择 Java Build Path,并添加所需的库:
- 点击 Add Library > JUnit 并在 JUnit3 中选择添加 JUnit 支持
- 点击 Add External JARs ...
- 在 <android-sdk>/platforms/directory 中选择最新版的 SDK。 同样在该目录下,选择下列文件:uiautomator.jar 和 android.jar
如果您使用的是其他开发环境,请确保将 android.jar 和 uiautomator.jar 文件添加至项目设置中。
创建脚本
使用 Java 类在以前创建的文件中创建一个项目, 称其为:SendMessage。 该类继承了 UiAutomatorTestCase 类,使用 Ctrl + Shift + o 键(适用于 Eclipse),添加所需的库。
创建三个函数来测试该应用:
- 搜索并运行该应用
- 发送短信
- 退出应用的主功能表
创建一个可帮助我们运行上述所有特性的函数 — 一种主要函数:
查找并运行应用的函数
该函数非常简单。 按主页按钮,显示主窗口后,打开功能表并查找该应用的图标。 点击该图标启动应用。
01 | private void findAndRunApp() throws UiObjectNotFoundException { |
03 | getUiDevice().pressHome(); |
05 | UiObject allAppsButton = new UiObject( new UiSelector() |
06 | .description( "Apps" )); |
08 | allAppsButton.clickAndWaitForNewWindow(); |
10 | UiObject appsTab = new UiObject( new UiSelector() |
15 | UiScrollable appViews = new UiScrollable( new UiSelector() |
18 | appViews.setAsHorizontalList(); |
20 | UiObject settingsApp = appViews.getChildByText( new UiSelector() |
21 | .className( "android.widget.TextView" ), "Messaging" ); |
23 | settingsApp.clickAndWaitForNewWindow(); |
26 | UiObject settingsValidation = new UiObject( new UiSelector() |
27 | .packageName( "com.android.mms" )); |
28 | assertTrue( "Unable to detect Messaging" , |
29 | settingsValidation.exists()); |
所有的类名、按钮上的文本等均来自 uiautomatorviewer。
发送短信
该函数可查找并按下编写新应用的按钮,输入接收文本信息的电话号码并按发送按钮。 电话号码和文本可通过函数参数发送:
01 | private void sendMessage(String toNumber, String text) throws UiObjectNotFoundException { |
03 | UiObject newMessageButton = new UiObject( new UiSelector() |
04 | .className( "android.widget.TextView" ).description( "New message" )); |
05 | newMessageButton.clickAndWaitForNewWindow(); |
08 | UiObject toBox = new UiObject( new UiSelector() |
09 | .className( "android.widget.MultiAutoCompleteTextView" ).instance(0)); |
10 | toBox.setText(toNumber); |
12 | UiObject textBox = new UiObject( new UiSelector() |
13 | .className( "android.widget.EditText" ).instance(0)); |
14 | textBox.setText(text); |
17 | UiObject sendButton = new UiObject( new UiSelector() |
18 | .className( "android.widget.ImageButton" ).description( "Send" )); |
显示电话号码和文本信息的字段没有任何特殊功能,因为这些字段的任何文本和描述都不可用。 因此,通过按照元素在界面层级中的排列位置在该实例中来使用元素,便可找到它们。
为了添加将参数传递至脚本的功能,我们可以指定想要发送该信息的位置的数量以及文本信息。 该函数测试()可初始化默认设置,如果系统通过命令行向任何参数发送信息,则可使用以下默认设置进行替换:
02 | String toNumber = "123456" ; |
03 | String text = "Test message" ; |
05 | String toParam = getParams().getString( "to" ); |
06 | String textParam = getParams().getString( "text" ); |
09 | toNumber = toParam.trim(); |
11 | if (textParam != null) { |
12 | text = textParam.trim(); |
因此,我们可以使用小密钥 -e、参数的第一个名称和第二个值通过脚本中的命令行来传递参数。 例如,我的应用可以发送数字来发送 " 777777 »:-e 至 777777
您可能会遇到一些问题。 例如,该应用不理解一些字符而失败。 包含某些字符的文本无法传达,因为无法理解而失败。 比如:空格键、&、<、>、(、) 、"、' 以及一些 Unicode 字符。 当使用这些字符编写字符串(如空间线:blogspaceblog)时,我将更换这些字符。 因此,当脚本启动 UiAutomator 时,我们将使用能够处理我们的输入参数的脚本。 我们添加了函数测试(),通过该测试,我们可以检查是否有选项、解析参数,如果有,可将其更换为实际字符。 以下是一个示例代码,演示了我们之前插入的内容:
02 | toParam = toParam.replace( "blogspaceblog" , " " ); |
03 | toParam = toParam.replace( "blogamperblog" , "&" ); |
04 | toParam = toParam.replace( "bloglessblog" , "<" ); |
05 | toParam = toParam.replace( "blogmoreblog" , ">" ); |
06 | toParam = toParam.replace( "blogopenbktblog" , "(" ); |
07 | toParam = toParam.replace( "blogclosebktblog" , ")" ); |
08 | toParam = toParam.replace( "blogonequoteblog" , "'" ); |
09 | toParam = toParam.replace( "blogtwicequoteblog" , "\"" ); |
10 | toNumber = toParam.trim(); |
12 | if (textParam != null) { |
13 | textParam = textParam.replace( "blogspaceblog" , " " ); |
14 | textParam = textParam.replace( "blogamperblog" , "&" ); |
15 | textParam = textParam.replace( "bloglessblog" , "<" ); |
16 | textParam = textParam.replace( "blogmoreblog" , ">" ); |
17 | textParam = textParam.replace( "blogopenbktblog" , "(" ); |
18 | textParam = textParam.replace( "blogclosebktblog" , ")" ); |
19 | textParam = textParam.replace( "blogonequoteblog" , "'" ); |
20 | textParam = textParam.replace( "blogtwicequoteblog" , "\"" ); |
21 | text = textParam.trim(); |
退出应用的主功能表
该函数是我们所实施的函数中最简单的一个。 仅需按下回环中的一个按钮,直至其显示该按钮,即可创建一条新信息。
01 | private void exitToMainWindow() { |
03 | UiObject newMessageButton = new UiObject( new UiSelector() |
04 | .className( "android.widget.TextView" ).description( "New message" )); |
07 | while (!newMessageButton.exists()) { |
08 | getUiDevice().pressBack(); |
以下是我们的源代码:源代码
001 | package blog.send.message; |
002 | import com.android.uiautomator.core.UiObject; |
003 | import com.android.uiautomator.core.UiObjectNotFoundException; |
004 | import com.android.uiautomator.core.UiScrollable; |
005 | import com.android.uiautomator.core.UiSelector; |
006 | import com.android.uiautomator.testrunner.UiAutomatorTestCase; |
008 | public class SendMessage extends UiAutomatorTestCase { |
009 | public void test() throws UiObjectNotFoundException { |
011 | String toNumber = "123456" ; |
012 | String text = "Test message" ; |
014 | String toParam = getParams().getString( "to" ); |
015 | String textParam = getParams().getString( "text" ); |
016 | if (toParam != null) { |
017 | toParam = toParam.replace( "blogspaceblog" , " " ); |
018 | toParam = toParam.replace( "blogamperblog" , "&" ); |
019 | toParam = toParam.replace( "bloglessblog" , "<" ); |
020 | toParam = toParam.replace( "blogmoreblog" , ">" ); |
021 | toParam = toParam.replace( "blogopenbktblog" , "(" ); |
022 | toParam = toParam.replace( "blogclosebktblog" , ")" ); |
023 | toParam = toParam.replace( "blogonequoteblog" , "'" ); |
024 | toParam = toParam.replace( "blogtwicequoteblog" , "\"" ); |
025 | toNumber = toParam.trim(); |
027 | if (textParam != null) { |
028 | textParam = textParam.replace( "blogspaceblog" , " " ); |
029 | textParam = textParam.replace( "blogamperblog" , "&" ); |
030 | textParam = textParam.replace( "bloglessblog" , "<" ); |
031 | textParam = textParam.replace( "blogmoreblog" , ">" ); |
032 | textParam = textParam.replace( "blogopenbktblog" , "(" ); |
033 | textParam = textParam.replace( "blogclosebktblog" , ")" ); |
034 | textParam = textParam.replace( "blogonequoteblog" , "'" ); |
035 | textParam = textParam.replace( "blogtwicequoteblog" , "\"" ); |
036 | text = textParam.trim(); |
039 | sendMessage(toNumber, text); |
043 | private void findAndRunApp() throws UiObjectNotFoundException { |
045 | getUiDevice().pressHome(); |
047 | UiObject allAppsButton = new UiObject( new UiSelector() |
048 | .description( "Apps" )); |
050 | allAppsButton.clickAndWaitForNewWindow(); |
052 | UiObject appsTab = new UiObject( new UiSelector() |
057 | UiScrollable appViews = new UiScrollable( new UiSelector() |
060 | appViews.setAsHorizontalList(); |
062 | UiObject settingsApp = appViews.getChildByText( new UiSelector() |
063 | .className( "android.widget.TextView" ), "Messaging" ); |
065 | settingsApp.clickAndWaitForNewWindow(); |
068 | UiObject settingsValidation = new UiObject( new UiSelector() |
069 | .packageName( "com.android.mms" )); |
070 | assertTrue( "Unable to detect Messaging" , |
071 | settingsValidation.exists()); |
074 | private void sendMessage(String toNumber, String text) throws UiObjectNotFoundException { |
076 | UiObject newMessageButton = new UiObject( new UiSelector() |
077 | .className( "android.widget.TextView" ).description( "New message" )); |
078 | newMessageButton.clickAndWaitForNewWindow(); |
081 | UiObject toBox = new UiObject( new UiSelector() |
082 | .className( "android.widget.MultiAutoCompleteTextView" ).instance(0)); |
083 | toBox.setText(toNumber); |
085 | UiObject textBox = new UiObject( new UiSelector() |
086 | .className( "android.widget.EditText" ).instance(0)); |
087 | textBox.setText(text); |
090 | UiObject sendButton = new UiObject( new UiSelector() |
091 | .className( "android.widget.ImageButton" ).description( "Send" )); |
095 | private void exitToMainWindow() { |
097 | UiObject newMessageButton = new UiObject( new UiSelector() |
098 | .className( "android.widget.TextView" ).description( "New message" )); |
101 | while (!newMessageButton.exists()) { |
102 | getUiDevice().pressBack(); |
如要生成测试组件的配置文件,请从命令行运行以下命令:编译并运行测试 UiAutomator
- <android-sdk>/tools/android create uitest-project -n <name> -t <target-id> -p <path>
,其中 <name> 是为测试 UiAutomator 而创建的项目的名称(在我们的案例中: SendMessage);<target-id> 是所选的设备和 Android API Level(您可以获得已安装设备列表、组(<android-sdk>/工具/ android 列表目标);<path> 是包含该项目的目录的路径。 - 您必须导出环境变量 ANDROID_HOME:
- Windows*:
set ANDROID_HOME=<path_to_your_sdk> - UNIX*:
export ANDROID_HOME=<path_to_your_sdk>
- 进入包含项目文件的目录 build.xml(在第 1 步中生成),并运行命令:
ant build - 使用 adb push 将 JAR 文件复制至该设备:
adb push <path_to_output_jar> /data/local/tmp/
在我们的案例中,该命令为:
adb push <project_dir>/bin/SendMessage.jar /data/local/tmp/ - 运行该脚本:
adb shell uiautomator runtest /data/local/tmp/SendMessage.jar –c blog.send.message.SendMessage
关于作者
Egor Churaev (egor.churaev@intel.com)— 软件实习生
相关文章与资源: