2.2 App UI层面的自动化
除了上面介绍的基于接口的自动化,App UI层面的自动化也是一个重要的自动化技术。可以帮助快速地进行App功能的回归。考虑到功能的变动和维护的代价,实际中投入产出比较高的方式是针对相对稳定的功能进行快速的回归。也可以和后面讨论的持续集成结合,做新构建的验证。除了功能的自动化验证之外,UI自动化技术还有一些其他的价值,比如第4章专项测试中介绍了使用UI自动化技术和云测试平台来构造一套高效的兼容性测试方案,以及基于模糊测试思路和UI自动化建立的App稳定性测试平台。这些都是以App UI自动化技术为前提的。
另外,需要指出的是,对于UI自动化应该有一个合理的期望。很多刚接触App测试的人或者不了解App UI自动化的管理人员,可能提起App的测试技术,觉得最主要的就是App的自动化,而主要讨论的就是UI的自动化技术。其实在经历过一些大的项目之后,观念可能会有些不同。基于目前多个团队的实践来看,如果对于一个在快速发展中的App,UI自动化可能更适合一些基础功能的回归,而不是替代手工的功能测试,特别是对于新的功能点。
接下来讨论如何进行App UI的自动化测试,包括Android和iOS的做法。
2.2.1 Android的UI自动化技术
本节介绍Android UI自动化测试的一些基本的实现方式。由于各个项目的产品特性不同,这里不涉及UI自动化框架的封装。
2.2.1.1 UI Automator Viewer
在进行Android UI自动化测试之前,作为测试人员,应当了解待测App的UI,包括使用了哪些控件、控件的类名、控件的id,等等。获知这些信息之后才能写测试脚本进行自动化测试。
Android SDK提供了一个UI Automator Viewer工具来帮助我们获取这些信息。(需要安装Android SDK Tools、Revision 21或以上,Android SDK Platform、API 16或以上)这个工具提供了一个可视化界面展示当前设备上的各个控件的属性。按照下列步骤使用这个工具:
链接Android设备到计算机。
打开/tools/。
运行命令:uiautomatorviewer
点击“Device Screenshot”按钮,鼠标悬停到某一UI元素上面即可查看对应控件的详细属性,如图2-16所示。
注意,如果你在计算机上连接了多个设备,在运行UI Automator Viewer前需要通过设置环境变量指定需要查看的UI设备。具体方法如下:
通过命令 adb devices找出设备序列号。
设置环境变量。
Windows
set ANDROID_SERIAL=<上一步获取的设备序列号>
Linux
export ANDROID_SERIAL=<上一步获取的设备序列号>
此外,UI Automator Viewer还提供了一个功能,可以查看UIAutomator测试框架可能不支持的控件。只需要在界面上点击“Toggle NAF Nodes”按钮(右上方感叹号标记的按钮)即可查看。如果在使用UIAutomator过程中发现某个控件不能被自动化驱动,可以对照UI Automator Viewer工具的结果排查问题。
在熟悉了待测App的UI以后,我们需要选择一项或者多项测试方法来进行UI自动化测试。Android的UI自动化测试方法从技术角度来说,大致可以分为以下几种:
Instrumentation。
UIAutomator。```
基于Instrumentation/ UIAutomator的封装。
基于系统事件的自动化测试。
基于图像识别的自动化测试。
下面分别介绍这几种方法。
####2.2.1.2 Android JUnit测试
Instrumentation和UIAutomator都是基于JUnit的,所以在介绍它们之前,先以Android Studio 1.0为例,介绍如何进行Android的JUnit测试。
1)我们需要在项目源代码目录下创建一个包和测试类,如图2-17所示。
这个测试的代码很简单,判断变量1和变量2是否相等,如果不相等,断言出错。当然运行后的结果必然是出错。这里需要注意,由于是基于Junit,测试方法名字必须以test开头,否则JUnit无法辨识。
2)点击Run->Edit Configurations。在弹出窗口上点击左上角的“Add New Configurations”按钮(绿色十字图标)。选择Android Tests,如图2-18所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/0ec4cbf0ad17d8ef8f3a09493293df6fec6b6fbf.png" width="" height="">
</div>
3)按图2-19的配置设置新建的测试。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/ce9d40308cb67ee649e2a408b1b42f6d50095ac9.png" width="" height="">
</div>
其中Modules为源代码所在的Module。Test部分可以自由设置寻找测试类的范围,这里我们手动指定了测试的类。
4)在工具条上选择刚才新建的测试并点击运行,如图2-20所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/518f264a3b54d2339aed86d88056d2e10415a952.png" width="" height="">
</div>
5)选择设备后等待执行结果,最后结果如图2-21所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/739b7d94c419dba1a06bef62dcadcb39ba64e977.png" width="" height="">
</div>
从左边栏可以看到哪个测试方法出错,从右边栏可以看详细的调用栈。经过上面的步骤我们已经可以对App进行单元测试了。只需在测试类中引用相应的待测试类并调用其方法即可。
1. Instrumentation测试框架
Instrumentation可用来进行黑盒和白盒测试,是谷歌早期推出的UI自动化测试框架,所以向后兼容性最好,可以用来测试安装在低版本的Android操作系统上的App。但相对来说,对使用此方法的测试人员的技术要求较高。主要缺陷是此方法只能用来测试单个App,涉及跨App交互的情况此方法不能支持。(例如,测试场景为:点击App A上的一个按钮将导致App B的界面展现到屏幕上,再点击App B上的一个按钮后进行断言判断。)
需要使用Instrumentation来进行UI自动化测试时。我们只需修改前面JUnit测试部分的测试类的代码即可,其他步骤都相同。
一个简单的使用Instrumentation进行UI自动化测试的测试类例子如下:
public class MyTest extends ActivityInstrumentationTestCase2 {
SettingsActivity mMyActivity;
protected void setUp() throws Exception {
super.setUp();
mMyActivity = getActivity();
}
protected void tearDown() throws Exception { super.tearDown();}
public MyTest() {
super(SettingsActivity.class);
}
public void testCase1()
{
ActionBar mMyBar= mMyActivity.getActionBar();
String title= mMyBar.getTitle().toString();
assertEquals(title,"My Application");
}
}```
我们的测试类需要继承ActivityInstrumentationTestCase2,T为待测试的Activity类型。有两个方法值得关注,一个是setUp方法。这个方法在测试开始的时候会被调用一次。我们可以在这个方法中进行初始化操作。上述代码在这个方法中获取了当前Activity对象的引用。另外一个方法是tearDown方法。这个方法在测试结束的时候会被调用一次。如有需要,我们可以在这个方法中添加代码。
跟之前的JUnit测试类一样,我们的测试方法需要以test开头。在测试方法testCase1中,我们获取页面头部的文字描述,进行了校验。当页面标题不是“My Application”的时候,此测试失败。
上面的代码只是一个简单的例子,我们可以做更多的事情,例如使用Activity类的findViewById方法获取需要的控件并做一些操作,如按钮点击,给输入框添加输入等。在此不一一赘述。
2. UIAutomator测试框架
UIAutomator是谷歌新推出的UI自动化测试框架,需要API level 16和以上的操作系统才能使用。使用它能够测试跨App交互的场景,但是它也有缺点。除了兼容性外,对一些控件也很难定位到。其中最大的缺点是不支持WebView的自动化测试。如果你的App中大量使用了WebView,并且需要对其进行UI自动化测试,那么你可能不得不选择Instrumentation。
下面以Android Studio 1.0为例说明如何进行UIAutomator自动化测试:
1)在\platforms\android-XX下面找到uiautomator.jar。
2)将uiautomator.jar复制到项目的app/libs目录下。
3)测试项目Module的依赖项增加libs下的所有jar:
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:support-v4:21.0.3'
}```
4)假设我们需要测试的Activity是SettingsActivity (使用Android Studio 1.0自带项目模板Settings Activity创建)。增加测试类如图2-22所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/f0bba6681a792e57d692051a8eea3813ad83985e.png" width="" height="">
</div>
5)确保待测App已经安装到Android手机上。
6)修改测试类代码为:
public class MyTest extends UiAutomatorTestCase {
String packageName="com.example.test.myapplication";
String activityName="SettingsActivity";
protected void setUp() throws Exception {
getUiDevice().pressHome();
Runtime.getRuntime().exec("am start -a android.intent.action.MAIN -n "+packageName+"/."+activityName);
UiObject mainUi=new UiObject(new UiSelector().packageName(packageName));
assertTrue("App not started",mainUi.waitForExists(3000));
super.setUp();
}
protected void tearDown() throws Exception
{
super.tearDown();
}
public void testCase1()
{
UiObject uiToTest=new UiObject(new UiSelector().className("android.widget.CheckBox").instance(1));
try
{
uiToTest.click();
getUiDevice().waitForIdle(3000);
UiObject uiToAssert=new UiObject(new UiSelector().text("Ringtone").instance(0));
assertFalse("CheckBox not work",uiToAssert.isEnabled());
}
catch(UiObjectNotFoundException ex)
{
assertTrue("Cannot find checkbox",false);
}
}
}```
与Instrumentation类似,我们可以在setUp中写初始化的代码,在tearDown中写清理工作的代码。这里我们在setUp中进行了回到主界面的操作。以此作为测试的起点。这也是通常的做法。
另外,在初始化代码中,我们调用了am命令启动我们需要测试的App的Activity。这个放在初始化的目的通常是为了节省测试的时间。一些情况下,启动Activity的工作只需要一次,我们可以写很多个测试方法来测试这个启动后的Activity的不同测试点。
在测试方法testCase1中,我们执行了如下的操作:
寻找第二个勾选框并点击。
判断Ringtone文本是否为不可用状态(外观变为灰色)。
这里有一些技巧。例如getUiDevice().waitForIdle可以保证UI变更后才执行后续代码,UiObjectNotFoundException的捕捉等。
7)按上面“Android的JUnit测试”描述的第6步执行后在选择设备界面时选择Cancel。因为目前Android Studio 1.0不能很好地支持UIAutomator。我们只用它来生成测试apk包。
8)项目的build\outputs\apk下可以找到带有test字样的apk包,如图2-23所示。
9)将Android手机连接电脑,使用下列命令将apk包推送到手机上:
adb push app-debug-test-unaligned.apk /sdcard/test.apk```
10)使用下列命令进行UI Automator 测试:
adb shell uiautomator runtest /sdcard/test.apk
11)上述命令执行完毕后观察手机,能发现UI被自动进行了操作。等待操作完成后,查看测试结果,如图2-24所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/e3a34ed41adec631fe29cb1b7d44b275ca775bba.png" width="" height="">
</div>
由于篇幅所限,更具体的UIAutomator测试方法不再进行介绍。读者可参考Android官方文档进行学习。
3.基于Instrumentation/ UIAutomator的封装
除了上文提到的两个谷歌推出的原生UI自动化测试框架外,还有很多基于它们封装的UI自动化测试框架。其中Robotium、Appium是使用比较广泛的。
Robotium其实严格来说算不上是一个测试框架,它只是一个类库,提供了更友好的定位控件的方法等,在使用Instrumentation进行自动化测试时,建议使用Robotium类库提升测试效率。
Appium是一个重量级的测试框架,支持iOS和Android等平台的自动化测试,支持多语言编写测试脚本。在Android自动化测试的实现上,它也是基于Instrumentation和UIAutomator的。Appium用Ruby写的测试脚本示例如图2-25所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/634b95e0f7a18f4c3dbcd91303dc8a7a6ce4a1d5.png" width="" height="">
</div>
由于篇幅所限,本书不赘述它们具体的使用方法。有兴趣的读者可以自行搜索相关的资料。
####2.2.1.3 基于系统事件的自动化测试
所有Android操作系统的输入事件都可以在进入adb shell后通过getevent获取(需要su),相关示例如图2-26所示。
这些事件都会存储在/dev/input/eventX下。每个eventX代表一个输入外设。例如触摸屏、键盘等,而输出的每一行代表一个事件,例如某个键盘按下,等等(具体每个事件的三个部分代表什么含义请参考Linux相关资料)。因此,我们可以通过解析这些文件,来实现录制的功能。测试人员可以把自己的操作录制下来,用一种数据格式记录。
那么怎么回放录制的测试脚本呢?我们可以解析录制的数据,并在adb shell下使用sendevent命令向指定外设一个个发送事件,也可以在adb shell下用input keyevent命令来做。例如,在将Android手机连接计算机后打开一个记事本类程序,进入adb shell,执行:
<div style="text-align: center"><img src="https://yqfile.alicdn.com/454621741fe779628747aeb664f6146527f56c3d.png" width="" height="">
</div>
Input keyevent 34```
你会发现效果等同于输入一个F键。
基于系统的事件无法对界面上的元素做判断,只能进行一些驱动类型的工作。因此这个方法不能单独使用来进行自动化测试。断言部分需要配合Instrumentation/UIAutomator。
此方法的缺陷是:
不同的设备有不同的分辨率。在A设备下录制的脚本在B设备下执行可能会产生问题。
不同设备的eventX可能代表不同外设。
这些都是在使用这个方法进行自动化测试的时候需要考虑的问题。
2.2.1.4 基于图像识别的自动化测试
近年来图像识别技术有了一定的发展。因此测试人员也开始研究是否可以将此技术应用到自动化测试中。简单的想法是,如果一个App的各个元素外观变化不大,而位置变化较为频繁,或者内部实现(例如UI控件类的变化)改变较为频繁,使用图像识别来写测试脚本的稳定性可能会较高。另外如果断言是基于图像的,那么使用图像识别技术来做自动化测试更为自然和方便。
测试方法举例:
1)测试人员写测试脚本,指定需要操作哪些元素(指定图片相关的特征),并说明怎么操作。
2)使用adb screencap命令截图。
3)使用图像识别技术来识别出各个元素的位置。
4)解析测试脚本,找出下个操作步骤。如果测试结束,则退出生成报告。
5)采用某方法进行相应的操作(通过系统事件、机器人手臂等方式驱动),或者进行基于图像识别的断言。
6)返回步骤2)。
但是由于使用此方法的优势只在特定场景能够体现,局限性较大,再加上图像识别准确率的问题,目前还没有被广泛接受和应用的测试框架出现。在此提及也是抛砖引玉,希望有一天能够有一个出色的基于图像识别的Android测试框架问世。
2.2.2 iOS的UI自动化技术
关于iOS的UI自动化,这里结合实例介绍两个方面的内容,一个是基于Instrument的UI自动化,另一个是目前比较常用的Appium框架。
2.2.2.1 基于Instrument的iOS UI自动化
对于iOS的UI自动化,Instrument 提供了最基本的自动化测试功能。我们可以通过Automation工具实现基本的自动化测试需求。该工具支持真机和模拟器两种测试方式:
模拟器:执行速度快,无须证书, 测试门槛较低, 但对于一些特定功能如相机、跳转就无能为力了。
真机:App的所有功能均能自动化实现,但需调试证书,且执行效率低。
下面用一个示例介绍整个使用过程。
1. 开始使用Automation
下面我们以Xcode 6为例, 介绍使用Automation的步骤。
1)选择要测试的target, 选定要编译安装的平台,若在真机上测试就选择真机设备,同时指定好调试证书及对应的provision文件。若在模拟器上测试就选择模拟器环境,我们这边指定iPhone6模拟器,如图2-27所示。
2)在刚才选择界面,点击Edit Scheme,指定Profile选项以Debug模式编译,点击关闭,如图2-28所示。
3)点击Product菜单下的Profile选项来编译项目工程,如图2-29所示。
4)等待编译完成后,弹出Instrument界面,选择Automation,如图2-30所示。
5)系统自动选择模拟器中的测试App,如果要改成真机运行,修改界面上方的测试平台至真实设备,选择相应的App即可,如图2-31所示。
2. 录制脚本
我们可以通过录制的方式生成测试脚本,切换到Script界面,点击下方的录制按钮,App在模拟器中就启动了,我们对App的操作,就会被自动录制成回放脚本,录制完成后,点击终止按钮,结束录制。点击运行按钮,就可以执行回放刚才录制的操作了,如图2-32所示。
- 编写自己的脚本
录制的方式虽然很方便,但是缺乏灵活性。大多数时候,我们需要自己编写脚本。我们的测试App是一个很简单的登录界面,输入用户名panxiaoming,密码:Ilovetest,就会登录成功,弹出一个欢迎界面,如图2-33所示。
如果输入错误的密码,会提示错误信息,如图2-34所示。
输入正确的密码,提示欢迎信息,如图2-35所示。
我们可以通过logElementTree的方法查看App的UI元素布局。点击运行,在Editor Log界面中查看这些UI元素,如图2-36所示。
Editor Log很有用,在这里可以查看脚本中所有的log信息以及断言结果,同时它会自动记录点击操作和一些其他的操作,同时还有截图,方便查看。
于是我们就可以编写一个正常登录的测试脚本,脚本如图2-37所示。
执行结果如图2-38所示。
我们看到在自己编写的脚本中加入逻辑和判断,使得脚本的可靠性更强,同时log信息可以帮助我们去分析定位问题,这种方式要比简单录制强上不少,脚本的语句类似JavaScript,可以到苹果官网查看具体语法和API。
4. Automation的高级用法
我们完全可以通过shell脚本去驱动执行Automation的测试用例, 通过定时执行shell脚本来完成我们的自动化测试任务,具体方法如下:
instruments -t <automationTracetemplate路径> <模拟器App路径> -e UIASCRIPT <testcase路径>```
我们只需要在自动化测试代码中使用performTaskWithPathArgumentsTimeout的方法把断言执行结果通过shell脚本写到外部文件中,就可以查看所有case的执行结果了。
5. Automation的总结
Instrument的Automation总体来说还是比较强大的,首先它不需要对App作任何修改、插桩,保证了App的原生功能。其次对iOS的控件支持非常好,同时支持暂停、截图等一系列功能,基本上能够满足我们对自动化测试的需要。不过它也有不少缺点,首先它没有case管理概念,其次是它的扩展性不是很好,还有就是它的执行经常会中断。
####2.2.2.2 自动化测试框架之Appium
Appium是当前比较流行的一个自动化测试框架。它是一个开源的自动化测试框架,支持跨平台,支持原生和混合开发,支持真机和模拟器。它是一个C/S结构的设计,底层是基于iOS的UIAutomation, 它的特点是:
无需任何驱动桩的插入,可直接操作原App。
case支持多种语言。
内含丰富的API,支持更多的手机端的操作。
支持各种测试框架。
下面简单介绍一下Appium在模拟器上的使用方法:
1)先到bitbucket.org/appium/appium.app/downloads/ 下载appium.app程序,记得下载OS X的版本。
2)安装该App到你的Mac机器上。
3)启动App,界面显示如图2-39所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/32f55f81d896256a39156a719b105d8d8c5fcc67.png" width="" height="">
</div>
4)首先我们需要检测当前环境,点击 ,Appium会自动帮你检测当前环境,是否有需要安装的依赖的工具。如果环境没问题会显示如图2-40所示界面(这里因为没有安装Android的环境,所以报错,可以忽略)。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/0d014f74c803fbb9e25dae1af5416dbec38e2a56.png" width="" height="">
</div>
5)因为我们需要测试iOS的App,所以应勾选苹果的图标,点击该图标,进入iOS的设置界面,如图2-41所示。在这里设置我们要测试的项目工程编译完的.app文件的路径,以及模拟器和iOS版本。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/10ec0083f5a366d044dcabcc4360856b575ee7b8.png" width="" height="">
</div>
6)点击 进入常规设置,如图2-42所示,在这里设置Appium Server的地址和端口信息,并设置本地地址和默认端口,同时勾选回调的地址和端口。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/51b954ba4b076fffd58b999747f07f7f16c522de.png" width="" height="">
</div>
7)点击Lauch就可以启动Appium Server了。点击 就可以去捕获UI控件了,我们可以看到模拟器被启动了,同时之前设置的App也被启动了。
8)在控件捕获界面,我们可以通过点击右边的截屏来获取控件的xpath,如图2-43所示。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/b066235eb67cfdf68a43b78a9fb4e27e3924385a.png" width="" height="">
</div>
9)同时可以点击录制,自动生成操作代码,如图2-44所示。录制界面支持预设的手势操作。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/1f7b0fd50794d1bf22b32c3371cd63481be2a1d2.png" width="" height="">
</div>
10)我们可以用自己熟悉的语言编写自己的测试用例,然后去执行这些用例,图2-45为在Eclipse里执行Java的测试用例。
本节大致介绍了Appium的基本功能和使用方法,由于篇幅有限,这里就不作详细介绍了。
<div style="text-align: center"><img src="https://yqfile.alicdn.com/a816e26ffb00e6fe28be5c5cdcdb146825dbb391.png" width="" height="">
</div>