解放双手——Android自动化测试

解放程序猿宝贵的右手(或者是左手)

http://blog.csdn.net/eclipsexys/article/details/45622813

——Android自动化测试技巧

Google大神镇楼 : http://developer.android.com/tools/testing-support-library/index.html#UIAutomator

前言: 
觉得文章太长不想往后翻的朋友,你们会后悔的,当然,你也可以选择先看后面的,你会觉得很爽,但是相信我,你还是会回来看前面的。那么,还是慢慢往后翻吧。

导入: 
人们懒的走路,才创造了汽车; 
人们懒的爬楼,才创造了电梯; 
人们懒的扫地,才创造了自动扫地机器人。 
人类的进步,离不开这些喜欢偷懒的人,现在,程序猿将偷懒上升到了一个新的高度——利用程序来进行自动化软件测试,将测试工程师从繁琐的测试用例中解脱出来,从此可以一边喝着咖啡,一边看着程序自动测试,不必看着测试用例重复无数次的测试步骤,也不必担心操作失误而导致不必要的错误,更不用担心压力测试而导致的身心俱疲。想了解程序猿是如何实现自动化测试的吗,这里有你想要的答案。

声明 
转载真的请注明出处: 
http://blog.csdn.net/eclipsexys

顺便打个广告: 
我的慕课网视频: http://www.imooc.com/space/teacher/id/347333

为啥要测试

  • 发现错误、为程序员提供修改意见
  • 验证软件是否满足设计需求和技术需求
  • 验证生产环境下真实的用户使用过程,分析用户体验

——总而言之一句话——软件测试,决定着软件的质量。

以前在TCL的时候,每个软件版本都要不停的跑MonkeyTest,一个是检测系统ROM的稳定性,一个是检测各种第三方应用在ROM上的使用情况,所以经常会报出很多Monkey跑出来的Bug,这些Bug经过我们分析,会初步判断是第三方App的问题还是系统的ROM问题,如果是第三方的问题,我们也会提交给App的运营商,但是大部分的运营商给我们的回复都是,我们的App不支持跑Monkey,其实Monkey可以发现一些潜在的问题,特别是一些很难复现的问题,我以前的leader曾经说过一句话我觉得非常好,没有什么bug是不能复现的,没有复现,只是没有找到必先的步骤,所以每一个bug都不是偶然的,我们应该尽量严谨的分析每一个可能存在的bug。

再以前的时候,对日的公司对测试更是无比看重,各种UT测试式样书,不仅仅是要写好怎么测试、测试什么,而且测试的数据、中间过程还要截图,保留证据。

有哪些测试

  • Google CTS测试:兼容性测试,测试ROM的兼容性标准
  • Google GTS测试
  • 实验室机器人测试、机械臂自动化模拟测试
  • Monkey Test压力测试
  • End User终端用户测试

对于美国的手机运营商,例如T-Mobile、Sprite、AT&T,他们都有一系列的手机性能测试,他们的测试项目、测试方法、测试过程,其实都是他们的商业机密,一个是保证测试结果的严谨性,一个也保证了手机厂商能够不作弊的完成测试,所以,千万不要学华X手机,在T-Mobile实验室偷拍手机测试机器人的软件、技术参数及其他机密信息,而被T-Mobile列入北美黑名单。逗比新闻

Android自动化测试工具

自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程

  • 将大量重复的测试步骤用脚本代替,让机器完成重复工作
  • 规范测试用例,保证测试质量
  • 高——大——上

自动化测试的工具

  • MonkeyRunner 
    monkeyrunner工具提供一个API来控制Android设备。可以写一个Python脚本来安装应用,运行应用,发送键值,截图。monkeyrunner对python进行了封装,加入了一些针对Android设备的类。可以完全用python脚本来实现这些功能。

  • Instrumentation 
    基于Android单个Activitiy的测试框架。

  • Robotium 
    一个优秀的测试框架,基于Instrumentation的二次封装。

  • QTP 
    一个Web上的自动化测试工具,通过录制脚本来实现自动化测试。

  • UiAutomator 
    目前最佳的UI自动化测试框架。基于Android 4.X+系统,专业UI自动化测试,可以模拟用户对手机的各种行为。编写快速、可以使用大部分的Android API、无需签名,无任何Activity限制。


各个测试框架的优缺点如下表所示:

测试框架 使用语言 运行方式 限制 适用环境
MonkeyRunner Python ADB、Python 测试靠坐标 压力测试
Instrumentation Java ADB 只能单个Activity测试,且需要应用相同签名,代码量大 白盒测试
Robotium 同上 同上 同上 同上
UiAutomator Java ADB或者脱机 Android 4.X+ UI测试


综上所述,我们使用UiAutomator作为我们Android自动化测试的首选框架。

UiAutomator环境搭建

开发环境:eclipse(非常抱歉,还没学会如何使用AS来开发Java代码、进行jar打包,请了解的朋友留言!!!) 
编译环境:Ant、Java、Android SDK

UiAutomator基本对象之UiDevice

通常用于获取系统的设备信息、系统按键、全局操作等。

获取坐标参数

返回值 方法 解释
boolean click(int x, int y) 在点(x, y)点击
int getDisplayHeight() 获取屏幕高度
int getDisplayWidth() 获取屏幕宽度
Point getDisplaySizeDp() 获取显示尺寸大小

系统信息

返回值 方法 解释
void getCurrentPackageName() 获取当前界面包名
void getCurrentActivityName() 获取当前界面Activity
void dumpWindowHierarchy(fileName) dump当前布局文件到/data/local/tmp/目录

滑动、拖拽

返回值 方法 解释
boolean drag(startX, startY, endX, endY, steps) 拖拽坐标处对象到另一个坐标
boolean swipe(segments, segmentSteps) 在Points[]中以segmentSteps滑动
boolean swipe(startX, startY, endX, endY, steps) 通过坐标滑动

系统按键

返回值 方法 解释
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() 打开快速设置

等待窗口

返回值 方法 解释
void waitForIdle() 等待当前窗口处于空闲状态、默认10s
void waitForIdle(long timeout) 自定义超时等待当前窗口处于空闲状态
boolean waitForWindowUpdate(packageName, timeout) 等待窗口内容更新

示例代码

// 输入按键 UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_A); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_B); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_C); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_A,1); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_B,1); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_C,1); // 点击 UiDevice.getInstance().click(400, 400); int h=UiDevice.getInstance().getDisplayHeight(); int w=UiDevice.getInstance().getDisplayWidth(); UiDevice.getInstance().click(w/2, h/2); // Swipe、Drag int startX, startY, endX, endY, steps; startX=300; startY=400; endX=startX; endY=startY + 200; steps=100; UiDevice.getInstance().drag(startX, startY, endX, endY, steps); int h=UiDevice.getInstance().getDisplayHeight(); int w=UiDevice.getInstance().getDisplayWidth(); UiDevice.getInstance().swipe(w, h/2, 30, h/2, 10); Point p1=new Point(); Point p2=new Point(); Point p3=new Point(); Point p4=new Point(); p1.x=250;p1.y=300; p2.x=600;p2.y=350; p3.x=800;p3.y=800; p4.x=200;p4.y=900; Point[] pp={p1,p2,p3,p4}; UiDevice.getInstance().swipe(pp, 50); // 灭屏、亮屏 UiDevice.getInstance().sleep(); UiDevice.getInstance().wakeUp(); // Notification UiDevice.getInstance().openNotification(); sleep(3000); UiDevice.getInstance().openQuickSettings(); UiDevice.getInstance().dumpWindowHierarchy("ui.xml");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

送个视频,让大家真实体验下:

视频代码:

        UiDevice.getInstance().pressBack();
        UiDevice.getInstance().pressBack();
        UiDevice.getInstance().pressHome();
        sleep(1000); UiDevice.getInstance().pressMenu(); sleep(1000); UiDevice.getInstance().pressBack(); sleep(1000); UiDevice.getInstance().pressRecentApps(); sleep(1000); UiDevice.getInstance().pressHome(); sleep(1000); UiDevice.getInstance().click(240, 1100); sleep(2000); UiDevice.getInstance().click(670, 1100); sleep(2000); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_H); sleep(1000); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_H, 1); sleep(1000); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_J); sleep(1000); UiDevice.getInstance().pressKeyCode(KeyEvent.KEYCODE_J, 1); sleep(1000); UiDevice.getInstance().swipe(30, 400, 600, 400, 10); sleep(1000); UiDevice.getInstance().pressHome(); sleep(1000); UiDevice.getInstance().drag(660, 860, 360, 360, 50); sleep(1000); UiDevice.getInstance().sleep(); sleep(1000); UiDevice.getInstance().wakeUp(); sleep(1000); UiDevice.getInstance().swipe(370, 1000, 370, 200, 50); sleep(1000); UiDevice.getInstance().takeScreenshot(new File("/sdcard/uidevice.png"));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

UiAutomator基本对象之UiSelector

通常使用UiSelector,通过各种属性节点和关系来定位组件,类似SQL语句的where条件。

uiautomatorviewer

要查看界面UI元素的层级关系,我们需要使用SDK/tools/下面的uiautomatorviewer工具来帮助我们进行查看,运行uiautomatorviewer,点击dump,我们就可以获取当前界面的UI快照。 
下面这张图就是一个示例: 

通过uiautomatorviewer,我们可以找到很多对象的属性,上图右下角的方框中的,都是对象所具有的属性。我们可以通过这些属性来定位需要的元素对象,这里要注意的是,uiautomator可以使用链式查找,即一个条件无法定位,那么可以通过多个条件组合,来定位一个元素。

通过text、description属性定位

返回值 方法 解释
UiSelector text(text) 通过text完全定位
UiSelector textContains(text) 通过text包含定位
UiSelector textMatches(regex) 通过text正则定位
UiSelector textStartsWith(text) 通过text起始文字定位
UiSelector description(text) 通过text完全定位
UiSelector descriptionContains(text) 通过description包含定位
UiSelector descriptionMatches(regex) 通过description正则定位
UiSelector descriptionStartsWith(text) 通过description起始文字定位

通过resourceId定位

返回值 方法 解释
UiSelector resourceId(id) 通过resourceId定位
UiSelector resourceIdMatches(regex) 通过resourceId正则定位

通过class、package定位

这种方式适用于当前页面上只有一种类型的组件的情况,例如只有一个ListView。

返回值 方法 解释
UiSelector className(className) 通过class定位
UiSelector classNameMatches(regex) 通过class正则定位
UiSelector packageName(name) 通过package定位
UiSelector packageNameMatches(regex) 通过package正则定位

通过index、instance定位

返回值 方法 解释
UiSelector index(index) 通过index定位
UiSelector instance(instance) 通过instance定位

通过其它属性定位

返回值 方法 解释
UiSelector enabled(val) 通过enabled属性定位
…… …… ……

对象的所有属性都可以使用,这里不再列举。

示例代码

// 找到对象 点击对象 UiSelector l=new UiSelector().text("联系人"); UiObject object=new UiObject(l); object.click(); // 匹配方式 // 完全匹配:联系人 // 包含匹配:系人 // 正则匹配:.*系.* // 起始文字匹配:联系 UiSelector l=new UiSelector().textContains("系人"); UiSelector l=new UiSelector().textMatches(".*系.*"); UiSelector l=new UiSelector().textStartsWith("联系"); UiObject object=new UiObject(l); object.click();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

UiAutomator基本对象之UIObject

UIObject是UiAutomator的核心属性之一。它代表了整个UI界面中的所有对象元素。 
它的功能包括:获取UI元素,点击、拖拽、滑动、对象属性判断、手势等。

点击与长按

返回值 方法 解释
boolean click() 点击对象
boolean clickAndWaitForNewWindow() 点击对象并等待新窗口出现
boolean clickAndWaitForNewWindow(timeout) 点击对象并等待新窗口出现,指定延迟
boolean clickBottomRight() 点击对象右下角
boolean clickTopLeft() 点击对象左上角
boolean longClick() 长按对象
boolean longClickBottomRight() 点击对象右下角
boolean longClickTopLeft() 点击对象左上角

拖拽与滑动

返回值 方法 解释
boolean dragTo(destObj, steps) 以steps拖动对象到destObj
boolean dragTo(destX, destY, steps) 以steps拖动对象到坐标
boolean swipeDown(steps) 向下拖动
boolean swipeLeft(steps) 向左拖动
boolean swipeRight(steps) 向右拖动
boolean swipeTop(steps) 向上拖动

文本输入与清除

返回值 方法 解释
boolean setText(text) 设置内容为text
boolean clearTextField() 清除文本

获取对象属性

返回值 方法 解释
Rect getBounds() 获取对象矩形范围
int getChildCount() 获取子View数量
…… …… ……

还有很多,不列举了。

获取对象属性状态

返回值 方法 解释
boolean isCheckable() 获取对象checkable状态
…… …… ……

还有很多,不列举了。

获取对象存在状态

返回值 方法 解释
boolean waitForExists(timeout) 等待对象出现
boolean waitUntilGone(timeout) 等待对象消失
boolean exists() 对象是否存在

手势状态

返回值 方法 解释
boolean performMultiPointerGesture(touches) 执行单指手势
boolean performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps) 执行双指手势
boolean pinchIn(percent, steps) 双指向内收缩
boolean pinchOut(percent, steps) 双指向外张开

示例代码

// 拖拽 UiObject object1=new UiObject(new UiSelector().text("联系人")); UiObject object2=new UiObject(new UiSelector().text("图库")); object1.dragTo(300,1200, 10); object1.dragTo(object2, 30); object1.swipeUp(5); // 输入、清空 UiObject edit=new UiObject(new UiSelector() .resourceId("com.hjwordgames:id/edit_password")); edit.setText("xuyisheng"); sleep(2000); edit.clearTextField(); // 判断 UiObject wlan=new UiObject(new UiSelector() .resourceId("com.android.settings:id/switchWidget")); if(!wlan.isChecked()){ wlan.click(); } // 手势 UiObject object=new UiObject(new UiSelector() .resourceId("com.android.gallery3d:id/photopage_bottom_controls")); object.pinchIn(80, 20); object.pinchOut(80, 20); Point startPoint1, startPoint2, endPoint1, endPoint2; startPoint1=new Point(); startPoint2=new Point(); endPoint1=new Point(); endPoint2=new Point(); startPoint1.x=150;startPoint1.y=200; startPoint2.x=100;startPoint2.y=500; endPoint1.x=900;endPoint1.y=200; endPoint2.x=950;endPoint2.y=500; object.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, 50);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

再送一个视频、不收费:

视频代码:

        UiObject word = new UiObject(new UiSelector().text("沪江开心词场")); word.clickAndWaitForNewWindow(); UiObject username = new UiObject(new UiSelector().text("沪江用户名/邮箱/手机")); username.setText("xuyisheng"); sleep(1000); UiObject pwd = new UiObject( new UiSelector().resourceId("com.hjwordgames:id/edit_password")); pwd.setText("123465"); sleep(2000); pwd.clearTextField(); sleep(1000); pwd.setText("123465"); UiDevice.getInstance().pressBack(); sleep(1000); UiObject login = new UiObject(new UiSelector().text("登 录")); login.clickAndWaitForNewWindow(); UiDevice.getInstance().pressBack(); UiDevice.getInstance().pressBack(); word.dragTo(300, 300, 50); sleep(1000); word.swipeDown(50);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

UiAutomator基本对象之UIScrollable

专业处理滚动一百年。

滚动

返回值 方法 解释
boolean flingBackward() 步长为5快速向后滑动
boolean flingForward() 步长为5快速向前滑动
boolean flingToBeginning(maxSwipes) 不超过maxSwipes滑动到最前,步长为5
boolean flingToEnd(maxSwipes) 不超过maxSwipes滑动到最后,步长为5
boolean flingToEnd(maxSwipes) 不超过maxSwipes滑动到最后,步长为5
…… …… 同样还可以使用Scroll,不一一列举

获取列表子元素

返回值 方法 解释
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() 获取滑动无效区域(到顶部的百分比)

滚动方向

返回值 方法 解释
boolean setAsHorizontalList() 设置水平滚动
boolean setAsVerticalList() 设置垂直滚动

示例代码

// 滑动 UiScrollable scroll=new UiScrollable(new UiSelector().className("android.widget.ListView")); scroll.flingBackward(); scroll.flingForward(); scroll.flingToBeginning(20); scroll.flingToEnd(30); // 滑动到某元素 UiScrollable scroll=new UiScrollable(new UiSelector().className("android.widget.ListView")); UiObject baiQiang=scroll.getChildByText(new UiSelector().className("android.widget.TextView"), "zhujia"); baiQiang.click(); scroll.getChildByInstance(new UiSelector().className("android.widget.TextView"), 25).click(); // 滑动到某元素 UiScrollable scroll=new UiScrollable(new UiSelector().className("android.widget.ListView")); UiSelector selector=new UiSelector().text("zhujia"); UiObject object=new UiObject(selector); scroll.scrollIntoView(selector); scroll.scrollIntoView(object); scroll.scrollTextIntoView("zhujia"); scroll.scrollDescriptionIntoView("zhujia"); scroll.scrollToBeginning(50,5); scroll.scrollToEnd(50,5); // 滑动方向 UiScrollable scroll=new UiScrollable(new UiSelector().className("android.support.v4.view.ViewPager")); scroll.setAsHorizontalList(); scroll.scrollBackward(); sleep(2000); scroll.scrollForward(); sleep(2000); scroll.setAsVerticalList(); scroll.scrollForward();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

视频大放送:

视频代码:

        UiScrollable scrollable = new UiScrollable( new UiSelector().className("android.widget.ListView")); scrollable.flingForward(); sleep(500); scrollable.flingBackward(); sleep(500); scrollable.flingForward(); UiObject target = new UiObject(new UiSelector().text("德国工业就是这么强大!不得不服")); scrollable.scrollIntoView(target); target.click();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

UiAutomator基本对象之UICollection

通常用于获取满足某种搜索条件的组件集合,通过链式搜索确定最终需要的组件。 
先按照一定的条件枚举容器内的子元素,再从符合条件的子元素中进一步定位。 
一般使用容器类组件作为父类,用于寻找不好定位的子元素。

示例代码

UiCollection collection=new UiCollection(new UiSelector().className("android.widget.ListView")); UiSelector childPattern=new UiSelector().className("android.widget.TextView"); String text="Music"; UiObject music=collection.getChildByText(childPattern, text); music.click();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

UiAutomator基本对象之UiWatcher

通常我们会让脚本来按照我们所需要的顺序来执行,但有时候,总有一些天灾人祸,比如10086发短信来了。 
所以,我们的脚本必须要有一定的容错性。

UiWatcher正是这样一个容错的对象,当我们在顺序执行脚本时,如果中间突然插入了一些不明事件,我们可以使用UiWatcher来拦截异常,处理完异常后,再返回原来的脚本执行顺序。

UiAutomator基本对象之Configuration

Configuration,自然是对默认操作的配置,通常情况下,我们使用默认的Configuration就足够了,当然,如果你有一些特殊需求,就可以通过Configuration类来设置。它能更改我们前面提到的所有默认属性的设置。包括默认延迟、输入延迟、等待超时等等。

UiAutomator基本对象之查看报告

下面是一个典型的UiAutomator测试报告:

INSTRUMENTATION_STATUS: numtests=1 INSTRUMENTATION_STATUS: stream= com.hj.autotest.AutoTest: INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner INSTRUMENTATION_STATUS: test=testDevice INSTRUMENTATION_STATUS: class=com.hj.autotest.AutoTest INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 1 INSTRUMENTATION_STATUS: numtests=1 INSTRUMENTATION_STATUS: stream=. INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner INSTRUMENTATION_STATUS: test=testDevice INSTRUMENTATION_STATUS: class=com.hj.autotest.AutoTest INSTRUMENTATION_STATUS: current=1 INSTRUMENTATION_STATUS_CODE: 0 INSTRUMENTATION_STATUS: stream= Test results for WatcherResultPrinter=. Time: 31.489 OK (1 test) INSTRUMENTATION_STATUS_CODE: -1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

这些报告被INSTRUMENTATION_STATUS_CODE分为了三个部分,1表示运行前,-1表示运行完成。

如果出错了,你可以在报告中找到相应的错误信息。

你同样需要知道的是,UiAutomator也是JUnit工程,你同样可以在里面使用断言来进行某些变量、结果值的测试,这些同样会在报告中体现出来。

最后,UiAutomator大部分内容都讲完了,最后一个视频:

视频代码:

        UiDevice.getInstance().pressHome();
        new UiObject(new UiSelector().description("Apps")) .clickAndWaitForNewWindow(); UiScrollable scrollable = new UiScrollable( new UiSelector() .resourceId( "com.google.android.googlequicksearchbox:id/apps_customize_pane_content")); scrollable.setAsHorizontalList(); UiObject word = new UiObject(new UiSelector().text("沪江开心词场")); while (!word.exists()) { scrollable.scrollForward(); } word.clickAndWaitForNewWindow(); UiObject username = new UiObject(new UiSelector().text("沪江用户名/邮箱/手机")); username.setText("xys10086"); sleep(1000); UiObject pwd = new UiObject( new UiSelector().resourceId("com.hjwordgames:id/edit_password")); pwd.setText("Aa123465"); sleep(1000); UiObject login = new UiObject(new UiSelector().text("登 录")); login.clickAndWaitForNewWindow(); if (new UiObject(new UiSelector().className( "android.widget.FrameLayout").index(1)).exists()) { new UiObject(new UiSelector().text("注册")) .clickAndWaitForNewWindow(); new UiObject( new UiSelector() .resourceId("com.hjwordgames:id/registerEditUsername")) .setText("xys100861"); new UiObject( new UiSelector() .resourceId("com.hjwordgames:id/registerEditPassword")) .setText("Aa123456"); new UiObject( new UiSelector() .resourceId("com.hjwordgames:id/regiserEditEmail")) .setText("[email protected]"); new UiObject(new UiSelector().text("确认注册")) .clickAndWaitForNewWindow(); UiObject ok = new UiObject( new UiSelector().resourceId("com.hjwordgames:id/btnOK")); if (ok.waitForExists(500)) { ok.clickAndWaitForNewWindow(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

如何使用UiAutomator

配置工程环境

在Eclipse中创建一个java工程,并添加platforms文件夹下面的android.jar和uiautomator.jar 两个引用。如下图:

创建测试用例

UiAutomator中的测试类都要继承UiAutomatorTestCase,每个测试用例的方法的方法名都要以test开头。如下图:

解放双手——Android自动化测试_第1张图片

在测试用例的方法中,我们就可以编写测试脚本代码。

生成build.xml文件

在终端中,输入:

android create uitest-project -n <name> -t <android-sdk-ID> -p <path> 
  • 1
  • 2
  • 1
  • 2

这里的android sdk id指的是在终端中,输入android list返回的你使用的sdk的id。 
这里还要PS下,一定要配置好环境变量,这是我们后面一键自动化的基础。

例如:

android create uitest-project -n Demo -t 30 -p "F:\EclipseWorkSpace\AutoTest" 
  • 1
  • 2
  • 1
  • 2

如下图:

修改build.xml文件

生成的build.xml文件我们还无法直接使用,我们需要修改它的一个属性,打开build.xml文件,将help改为build,如下图:

这里写图片描述

打包Jar

使用Ant,我们利用build.xml打包生成jar,命令如下:

ant -buildfile "F:\EclipseWorkSpace\AutoTest"
  • 1
  • 1

编译过程如下图:

解放双手——Android自动化测试_第2张图片

Push Jar包到手机

我们需要将jar包push到手机中的/data/local/tmp/目录才能启动测试。如下图:

adb push "F:\EclipseWorkSpace\AutoTest\bin\Demo.jar" /data/local/tmp/
  • 1
  • 1

执行测试用例

在终端中输入启动测试命令(#后如果不指定具体的用例名,则测试所有的方法),如下:

adb shell uiautomator runtest Demo.jar --nohup -c com.hj.autotest.AutoTest#testBrowser
  • 1
  • 1

到此为止,整个测试用例的测试就全部结束了。

让自动化测试自动起来

看完前面的步骤,相信很多人已经不想再看下去了,好吧,那你们损失大了,所谓自动化测试,就是为了减少人工的操作,像这样反复的编译、修改、push、运行,这跟手动去测试又有什么区别呢? 
OK,让自动化再上升一个境界。 
我们可以发现,其实这些操作,与我们进行测试一样,也是一些机械动作,ok,那么我们完全可以使用同样的思路——使用脚本来解决这些问题。 
我们创建一个脚本工具——UiAutomatorTool,来封装这些机械的步骤。代码非常简单,无非是使用Java调用终端命令,来执行前面的各种操作。 
代码如下:

package com.hj.autotest; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class UiAutomatorTool { // 工作空间目录 private static String WORKSPACE_PATH; /** * 指定自动测试的参数 * * @param jarName * 生成jar的名字 * @param testPackageclass * 测试包名+类名 * @param testFunction * 测试方法名,空字符串代表测试所有方法 * @param androidId * SDK id */ public UiAutomatorTool(String jarName, String testPackageclass, String testFunction, String androidId) { System.out.println("*******************"); System.out.println(" --AutoTest Start--"); System.out.println("*******************"); // 获取工作空间目录路径 WORKSPACE_PATH = getWorkSpase(); System.out.println("自动测试项目工作空间:\t\n" + getWorkSpase()); // ***********启动测试*********** // // 创建Build.xml文件 creatBuildXml(jarName, androidId); // 修改Build.xml文件中的Build类型 modfileBuild(); // 使用Ant编译jar包 antBuild(); // push jar到手机 pushJarToAndroid(WORKSPACE_PATH + "\\bin\\" + jarName + ".jar"); // 测试方法,为空则测试全部方法 if (androidId.equals("")) { runTest(jarName, testPackageclass); } else { runTest(jarName, testPackageclass + "#" + testFunction); } // ***********启动测试*********** // System.out.println("*******************"); System.out.println("---AutoTest End----"); System.out.println("*******************"); } /** * 创建build.xml文件 */ public void creatBuildXml(String jarName, String androidID) { System.out.println("--------创建build.xml 开始---------"); execCmd("cmd /c android create uitest-project -n " + jarName + " -t " + androidID + " -p " + "\"" + WORKSPACE_PATH + "\""); System.out.println("--------创建build.xml 完成---------"); } /** * 修改build.xml文件位build type */ public void modfileBuild() { System.out.println("--------修改build.xml 开始---------"); StringBuffer stringBuffer = new StringBuffer(); try { File file = new File("build.xml"); if (file.isFile() && file.exists()) { InputStreamReader read = new InputStreamReader( new FileInputStream(file)); BufferedReader bufferedReader = new BufferedReader(read); String lineTxt; while ((lineTxt = bufferedReader.readLine()) != null) { if (lineTxt.matches(".*help.*")) { lineTxt = lineTxt.replaceAll("help", "build"); } stringBuffer = stringBuffer.append(lineTxt).append("\t\n"); } read.close(); } else { System.out.println("找不到build.xml文件"); } } catch (Exception e) { System.out.println("读取build.xml内容出错"); e.printStackTrace(); } // 重新写回build.xml rewriteBuildxml("build.xml", new String(stringBuffer)); System.out.println("--------修改build.xml 完成---------"); } /** * 使用Ant编译jar包 */ public void antBuild() { System.out.println("--------编译build.xml 开始---------"); execCmd("cmd /c ant -buildfile " + "\"" + WORKSPACE_PATH + "\""); System.out.println("--------编译build.xml 完成---------"); } /** * adb push jar包到Android手机 * * @param localPath * localPath */ public void pushJarToAndroid(String localPath) { System.out.println("--------push jar 开始---------"); localPath = "\"" + localPath + "\""; System.out.println("jar包路径:" + localPath); String pushCmd = "adb push " + localPath + " /data/local/tmp/"; execCmd(pushCmd); System.out.println("--------push jar 完成---------"); } /** * 测试方法 * * @param jarName * jar包名 * @param testName * testName */ public void runTest(String jarName, String testName) { System.out.println("--------测试方法 开始---------"); String runCmd = "adb shell uiautomator runtest "; String testCmd = jarName + ".jar " + "--nohup -c " + testName; execCmd(runCmd + testCmd); System.out.println("--------测试方法 完成---------"); } /** * 获取WorkSpace目录 * * @return WorkSpace目录 */ public String getWorkSpase() { File directory = new File(""); return directory.getAbsolutePath(); } /** * Shell命令封装类 * * @param cmd * Shell命令 */ public void execCmd(String cmd) { System.out.println("ExecCmd:" + cmd); try { Process p = Runtime.getRuntime().exec(cmd); // 执行成功返回流 InputStream input = p.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader( input, "GBK")); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } // 执行失败返回流 InputStream errorInput = p.getErrorStream(); BufferedReader errorReader = new BufferedReader( new InputStreamReader(errorInput, "GBK")); String eline; while ((eline = errorReader.readLine()) != null) { System.out.println(eline); } } catch (IOException e) { e.printStackTrace(); } } /** * 重新写回Build.xml * * @param path * path * @param content * content */ public void rewriteBuildxml(String path, String content) { File dirFile = new File(path); if (!dirFile.exists()) { dirFile.mkdir(); } try { BufferedWriter bw1 = new BufferedWriter(new FileWriter(path)); bw1.write(content); bw1.flush(); bw1.close(); } catch (IOException e) { e.printStackTrace(); } } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204

那我们怎么使用呢?拿一个测试类来说:

public class AutoTest extends UiAutomatorTestCase { public static void main(String[] args) { new UiAutomatorTool("Demo", "com.hj.autotest.AutoTest", "testUiSelector", "30"); } …… }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们只需要在测试类中new一个UiAutomatorTool,并指定jar包名、包名、用例名、Android id即可。 
接下来,只需要运行这样Java程序,就完成了整个过程的自动化,一键编译、一键运行。

好吧,再来一个视频:

让偷懒更进一步

前面我们已经让编译、push、运行自动化了,但是说到底,就连编写脚本也是一件非常繁琐的事情啊。OK,我们同样可以创建一个H5的页面,通过编写图形化的页面,来替代我们每个动作脚本的编写,毕竟这些脚本也是死的啊。

让偷懒发扬光大

这些脚本可不仅仅能做测试。 
经过前面一系列的代码、演示,我们已经可以通过脚本来进行测试用例的自动化测试,但是,自动化不仅仅可以用来测试,当我们在调试程序的时候,经常需要登陆App以后才能进行测试,我们同样可以把这些操作放到脚本中,启动调试后,直接运行脚本,完成这样繁琐的输入、登陆步骤。

你可能感兴趣的:(解放双手——Android自动化测试)