UI Automator
UI Automator测试框架适合跨系统和已安装应用的跨应用功能性 UI 测试。利用 UI Automator API,您可以执行在测试设备中打开“设置”菜单或应用启动器等操作。UI Automator 测试框架非常适合编写黑盒自动化测试,其中的测试代码不依赖于目标应用的内部实现详情。
添加依赖
在build.gradle中添加:
代码语言:javascript复制dependencies {
...
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1'
}
检测设备上的UI
在进行测试时,我们需要根据控件属性来定位app上的UI控件,uiautomatorviewer tool用于快速获取UI控件的属性,可以在Android Device Monitor中点击Dump View Hierarchy For UI Automator按钮打开。
添加UI Automator测试类
在类定义的前面添加*@RunWith(AndroidJUnit4.class)注解,确保AndroidJUnitRunner*运行器可用。
- 获取 UiDevice对象。用于获取系统的设备信息、系统按键、全局操作等。
- 通过findObject()方法获取UiObject对象。它代表了整个UI界面中的所有对象元素。
- 调用UiObject对象的方法,模拟用户操作,重复2,3完成一系列用户操作。
- 测试UI状态是否符合期望。
UiDevice
获取坐标参数
代码语言:javascript复制boolean click(int x, int y) 在点(x, y)点击
int getDisplayHeight() 获取屏幕高度
int getDisplayWidth() 获取屏幕宽度
Point getDisplaySizeDp() 获取显示尺寸大小
系统信息
代码语言:javascript复制void getCurrentPackageName() 获取当前界面包名
void getCurrentActivityName() 获取当前界面Activity
void dumpWindowHierarchy(fileName) dump当前布局文件到/data/local/tmp/目录
滑动、拖拽
代码语言:javascript复制boolean drag(startX, startY, endX, endY, steps) 拖拽坐标处对象到另一个坐标
boolean swipe(segments, segmentSteps) 在Points[]中以segmentSteps滑动
boolean swipe(startX, startY, endX, endY, steps) 通过坐标滑动
系统按键
代码语言:javascript复制void wakeUp() 按电源键亮屏
void sleep() 按电源键灭屏
boolean isScreenOn() 亮屏状态
void setOrientationLeft() 禁用传感器,并左旋屏幕,固定
void setOrientationNatural() 禁用传感器,恢复默认屏幕方向,固定
void setOrientationRight() 禁用传感器,并右旋屏幕,固定
void unfreezeRotation() 启用传感器,并允许旋转
boolean isNaturalOrientation() 检测是否处于默认旋转状态
void getDisplayRotation() 返回当前旋转状态,0、1、、2、3分别代表0、90、180、270度旋转
void freezeRotation() 禁用传感器,并冻结当前状态
boolean takeScreenshot(storePath) 当前窗口截图、1.0f缩放、90%质量保存在storePath
void takeScreenshot(storePath, scale, quality) 同上,但指定缩放和压缩比率
void openNotification() 打开通知栏
void openQuickSettings() 打开快速设置
等待窗口
代码语言:javascript复制void waitForIdle() 等待当前窗口处于空闲状态、默认10s
void waitForIdle(long timeout) 自定义超时等待当前窗口处于空闲状态
boolean waitForWindowUpdate(packageName, timeout) 等待窗口内容更新
示例
下面示例获取UiDevice示例和模拟按下home键的操作。
代码语言:javascript复制import org.junit.Before;
import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.Until;
...
@RunWith(AndroidJUnit4.class)
@SdkSuppress(minSdkVersion = 18) //确保只测试API不小于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 mDevice;
@Before
public void startMainActivityFromHomeScreen() {
// Initialize UiDevice instance
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// Start from the home screen
mDevice.pressHome();
// Wait for launcher
final String launcherPackage = mDevice.getLauncherPackageName();
assertThat(launcherPackage, notNullValue());
mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
LAUNCH_TIMEOUT);
// Launch the app
Context context = InstrumentationRegistry.getContext();
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
mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
LAUNCH_TIMEOUT);
}
}
UiSelector
通过Device的findObject()或UiObject的构造方法获取特定的UIObject对象,两者参数都是一个UiSelector对象。
UiSelector通过控件属性或关系定位控件,可以链式调用组合多个条件。对象的属性都可以作为定位条件。
使用childSelector()方法可以查找到的控件的子空间里面的元素,参数也是一个UiSelector对象(示例2)。
使用资源ID作为条件是最稳妥的方式。
示例
代码语言:javascript复制UiObject cancelButton = mDevice.findObject(new UiSelector()
.text("Cancel"))
.className("android.widget.Button"));
UiObject okButton = mDevice.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();
}
代码语言:javascript复制UiObject appItem = new UiObject(new UiSelector()
.className("android.widget.ListView")
.instance(0)
.childSelector(new UiSelector()
.text("Apps")));
UiObject
调用UiObject的方法来模拟用户操作。
点击与长按
代码语言:javascript复制boolean click() 点击对象
boolean clickAndWaitForNewWindow() 点击对象并等待新窗口出现
boolean clickAndWaitForNewWindow(timeout) 点击对象并等待新窗口出现,指定延迟
boolean clickBottomRight() 点击对象右下角
boolean clickTopLeft() 点击对象左上角
boolean longClick() 长按对象
boolean longClickBottomRight() 点击对象右下角
boolean longClickTopLeft() 点击对象左上角
拖拽与滑动
代码语言:javascript复制boolean dragTo(destObj, steps) 以steps拖动对象到destObj
boolean dragTo(destX, destY, steps) 以steps拖动对象到坐标
boolean swipeDown(steps) 向下拖动
boolean swipeLeft(steps) 向左拖动
boolean swipeRight(steps) 向右拖动
boolean swipeTop(steps) 向上拖动
文本输入与清除
代码语言:javascript复制boolean setText(text) 设置内容为text
boolean clearTextField() 清除文本
获取对象属性
代码语言:javascript复制Rect getBounds() 获取对象矩形范围
int getChildCount() 获取子View数量
…… …… ……
获取对象属性状态
代码语言:javascript复制boolean isCheckable() 获取对象checkable状态
…… …… ……
获取对象存在状态
代码语言:javascript复制boolean waitForExists(timeout) 等待对象出现
boolean waitUntilGone(timeout) 等待对象消失
boolean exists() 对象是否存在
手势状态
代码语言:javascript复制boolean performMultiPointerGesture(touches) 执行单指手势
boolean performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps) 执行双指手势
boolean pinchIn(percent, steps) 双指向内收缩
boolean pinchOut(percent, steps) 双指向外张开
发送Intent,启动Activity
UI Automator 测试框架允许我们直接发送Intent或启动Activity,我们可以通过getContext()方法获取context。
示例
启动一个calculator app
代码语言:javascript复制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);
mDevice.wait(Until.hasObject(By.pkg(CALC_PACKAGE).depth(0)), TIMEOUT);
}
UiCollection
代表一个容器类控件,用于获取子控件。
示例
代码语言:javascript复制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();
UiScrollable
用于模拟滚动框的操作。
滚动
代码语言:javascript复制boolean flingBackward() 步长为5快速向后滑动
boolean flingForward() 步长为5快速向前滑动
boolean flingToBeginning(maxSwipes) 不超过maxSwipes滑动到最前,步长为5
boolean flingToEnd(maxSwipes) 不超过maxSwipes滑动到最后,步长为5
boolean flingToEnd(maxSwipes) 不超过maxSwipes滑动到最后,步长为5
…… …… Scroll也有同样的方法
tip:fling和scroll的区别在于fling监听速度,scroll监听距离。
获取列表子元素
代码语言:javascript复制boolean getChildByDescription(childPattern, text) 默认滚动,查找childPattern UiSelector所对应的text子元素
boolean getChildByDescription(childPattern, text, allowScrollSearch) 是否允许滚动,查找childPattern UiSelector所对应的text子元素
…… …… 还有text、instance同样可以使用,不一一列举。
boolean scrollIntoView(obj) 滚动到obj所处的位置
boolean scrollIntoView(selector) 滚动到条件元素所处的位置
boolean scrollTextIntoView(text) 滚动到文本对象所处的位置
boolean scrollToBeginning(maxSwipes) 滚动到开始位置
boolean scrollToBeginning(maxSwipes, steps) 指定步长,滚动到开始位置
boolean scrollToEnd(maxSwipes) 滚动到最后位置
boolean scrollToEnd(maxSwipes, steps) 指定步长,滚动到最后位置
boolean setMaxSearchSwipes(swipes) 设置最大可扫动次数
boolean getMaxSearchSwipes() 获取最大可扫动次数、默认30
UiScrollable setSwipeDeadZonePercentage(swipeDeadZonePercentage) 设置滑动无效区域(到顶部的百分比)
double getSwipeDeadZonePercentage() 获取滑动无效区域(到顶部的百分比)
滚动方向
代码语言:javascript复制boolean setAsHorizontalList() 设置水平滚动
boolean setAsVerticalList() 设置垂直滚动
示例
代码语言:javascript复制UiScrollable settingsItem = new UiScrollable(new UiSelector()
.className("android.widget.ListView"));
UiObject about = settingsItem.getChildByText(new UiSelector()
.className("android.widget.LinearLayout"), "About tablet");
about.click();
验证结果
InstrumentationTestCase 继承自 TestCase,所以我们同样用Assert方法验证控件状态是否符合期望。
示例
代码语言:javascript复制private static final String CALC_PACKAGE = "com.myexample.calc";
public void testTwoPlusThreeEqualsFive() {
// Enter an equation: 2 3 = ?
mDevice.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("two")).click();
mDevice.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("plus")).click();
mDevice.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("three")).click();
mDevice.findObject(new UiSelector()
.packageName(CALC_PACKAGE).resourceId("equals")).click();
// Verify the result = 5
UiObject result = mDevice.findObject(By.res(CALC_PACKAGE, "result"));
assertEquals("5", result.getText());
}
总结
接上一篇文章Android测试入门-1 ,我们了解了Unit test,Instrumentation工具类以及Android提供的两个自动化测试框架Espresso和UI Automator。
除了功能测试和UI测试外,常用的测试还有压力测试,Android SDK自带的测试工具Monkey,可以先系统发送伪随机的用户事件流,实现压力测试。使用比较简单:https://developer.android.com/studio/test/monkey.html
比较热门的测试框架还有Appium,相较于Android提供的测试框架,appium有额外的优点:支持Hybird App、Web App,支持iOS、Firefox OS。
http://appium.io/slate/en/master/?ruby#about-appium
参考文章
https://developer.android.com/training/testing/ui-testing/uiautomator-testing.html#setup
http://blog.csdn.net/eclipsexys/article/details/45622813
示例代码
https://github.com/googlesamples/android-testing/tree/master/ui/uiautomator/BasicSample