Android自动化测试基础
一、自动化框架介绍
1.Android常用的自动化测试工具框架:
Monkey,MonkeyRunner,UIAutomator,Robotium,Appium等等
但这些工具框架都是什么呢有什么联系呢,先通过一张图来了解一下吧.
1.1、Monkey
是Android SDK自带的测试工具,是一个命令行工具,可以运行在模拟器中或者实际设备中,它向系统发送伪随机的用户事件流(如按键输入,触摸屏输入,手势输入等),实现对正在开发的应用程序进行压力测试。由于测试事件和数据都是随机的,不能自定义,所以有很大的局限性。
1.2、MonkeyRunner
是Android SDK提供的测试工具。严格意义上来说MonkeyRunner其实是一个Api工具包,比Monkey强大,可以编写测试脚本来自定义数据、事件。缺点是脚本(jython(java语言))用Python(Python(C语言))来写,对测试人员来说要求较高,有比较大的学习成本;使用By id/name/text方法操控(hierarchyviewer),执行速度太慢。典型应用:安装,卸载,启动Activity,点击X,Y坐标,发送按键事件,drag,截屏等。
1.3、UIAutomator
是Android提供的自动化测试框架,基本上支持所有的Android事件操作。是用来做UI测试的,也就是普通的手工测试,点击每个控件元素看看输出的结果是否符合预期。对比Instrumentation它不需要测试人员了解代码实现细节(可以用UiAutomatorviewer抓去App页面上的控件属性而不看源码)。能跨App(比如:很多App有选择相册、打开相机拍照,这就是跨App测试)。缺点是只支持SDK 16(Android 4.1)及以上,不支持Hybird App、WebApp。
1.4、Robotium
是基于Instrumentation的测试框架,主要针对android平台的应用进行黑盒自动化测试,它提供了模拟各种手势操作(点击,长按,滑动等)、查找和断言机制的API,能够对各种控件进行操作。Robotium结合android官方提供的测试框架达到对应用程序进行自动化测试。另外,Robotium 4.0版本已经支持对WebView的操作。Robotium对Activity,Dialog,Toast,Menu都是支持的。支持Native app、Hybird App。缺点不能跨App。
录制工具Radar(不稳定)、Testin(不提供源码)、官网工具(收费)
开发环境:脚本语言java;Eclipse中引入:robotium-solo-**.jar
1.5、Appium (官网:http://appium.io/)
是开源的移动端自动化测试框架;支持Native App、Hybird App、Web App;支持Android、iOS、Firefox OS;是跨平台的可以在mac,windows以及linux系统上。用Appium自动化测试不需要重新编译App;脚本语言:Java、python、ruby、C#、Objective C、PHP等主流语言。
相关限制:如果你在Windows使用Appium,你没法使用预编译专用于OS X的.app文件,因为Appium依赖OS X专用的库来支持iOS测试,所以在Windows平台你不能测试iOS Apps。这意味着你只能通过在Mac上来运行iOS测试。
总结:在iOS部分是封装了UIAutomation;Android 4.2以上是用UiAutomator,Android 2.3 ~ 4.1用的是Instrumentation,也就说Appium同时封装了UiAutomator和Instrumentation。所以Appium拥有了以上几大框架的所有优点:跨App,支持Native App、Hybird App、Web App,还支持N种语言来编写你的测试脚本。
appium内核基于UiAutomator来识别元素。如果只有Android app产品,推荐选择robotium,提供的API比appium好用;既有IOS和Android,则使用appium
1.6、Instrumentation
是早期Google提供的Android自动化测试工具类,虽然在那时候JUnit也可以对Android进行测试,但是Instrumentation允许你对应用程序做更为复杂的测试,甚至是框架层面的。通过Instrumentation你可以模拟按键按下、抬起、屏幕点击、滚动等事件。Instrumentation是通过将主程序和测试程序运行在同一个进程来实现这些功能,你可以把Instrumentation看成一个类似Activity或者Service并且不带界面的组件,在程序运行期间监控你的主程序。缺点是对测试人员来说编写代码能力要求较高,需要对Android相关知识有一定了解,还需要配置AndroidManifest.xml文件,不能跨多个App。
二、UiAutomator环境搭建及详细操作
1、环境搭建
1.1 必备条件
1、JDK
2、SDK(API高于15)
3、Eclipse(安装ADT插件)
4、ANT(用于编译生成的jar)
1.2 简要步骤
1、安装JDK并添加环境变量
先建立JAVA_HOME变量,然后在path中添加%JAVA_HOME%\bin;
2、添加SDK环境变量
先建立ANDROID_HOME,然后在path中添加%ANDROID_HOME%\tools;
3、安装Eclipse,并安装ADT插件
4、安装ANT工具,并添加环境变量
先建立ANT_HOME变量,然后在path中添加%ANT_HOME%\bin
2、详细操作
2.1建立工程
用Eclipse新建Java Project
File->New->Java Project,输入工程名称点击"Finish"
2.2添加JUnit库
在工程名称上点击右键,Bulid Path->Configure Bulid Path
点击Next
点击Finish
2.3添加Android库
找到路径sdk\platforms\android-18下面的android.jar和uiautomator.jar添加进来(android-17以上):
所有库添加完成后如下图:
2.4在src中添加包,然后添加class文件
在新建的工程下的src上点击右键,New->Class
输入包名和类名,包名的命名规则:首字母为小写,类名:首字母英文大写,后面字母均为小写,多个单词组成,每个单词的首字母大写
点击Finish
在class文件中添加如下内容:
2.5找到SDK ID
cmd进入sdk\tools\ 目录下,运行命令:android list
查看API 大于15的SDK的ID值,当前是2;
2.6创建bulid文件
仍然在\sdk\tools\目录下,运行命令:
android create uitest-project -n <name> -t <android-sdk-ID> -p <path>
其中name为将来生成的jar包的名字,可以自己定义,android-sdk-ID为上一步骤看到的2,path是新建工程的路径名称
android create uitest-project -n AutoRunner -t 2 -p D:\adt-bundle-windows-x86_64-20140702\eclipse\workspace\ChpJavaTwo
运行命令后,将会在工程的根目录下生成build.xml文件
2.7编译生成jar
cmd进入项目的工程目录,然后运行ant build,使用ant编译生成jar,执行如下:
在bin目录下生成键jar文件
2.8 push并运行jar
adb push <jar文件路径> data/local/tmp
实际执行命令为
adb push D:\adt-bundle-windows-x86_64-20140702\eclipse\workspace\ChpJavaTwo\bin\AutoRunner.jar data/local/tmp
运行jar文件
adb shell uiautomator runtest <jar文件名> -c <包名.类名>
实际运行命令为
adb shell uiautomator runtest AutoRunner.jar -c testpackage.TestClass(注意不要写错包名和类名)
可以看到手机会按照Runner中的步骤自动执行。
三、项目实例
1.非触屏机实例(e350)
1.1类monkey压力测试
package testlaunch;
/*
* Function:RandomPress(TestMonkey350)
* Author:yangyanxin
* Date:2017.11.27
* */
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import android.os.RemoteException;
import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
public class TestMonkey350 extends UiAutomatorTestCase{
public void testRandomPress() throws UiObjectNotFoundException, RemoteException{
UiDevice device = UiDevice.getInstance();
//get a time object
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(df.format(new Date())+".testBegin\n");
int j = 0;
for (int i = 0; i < 10000; i++) {
device.wakeUp();
System.out.println(j+"times");
//get a random object
Random random = new Random();
//get a number which ranges from 0 to 5
int result = random.nextInt(6);
System.out.println("random="+result);
switch (result) {
case 0:
//press the key : POWER
device.pressKeyCode(26);//power
System.out.println("keycode:26-Power");
System.out.println(df.format(new Date())+"\n");
break;
case 1:
//press the key : FN
device.pressKeyCode(282);//fn
System.out.println("keycode:282-Fn");
System.out.println(df.format(new Date())+"\n");
break;
case 2:
//press the key : PTT
device.pressKeyCode(284);//ptt
System.out.println("keycode:284-Ptt");
System.out.println(df.format(new Date())+"\n");
break;
case 3:
//press the key : ROTATE
device.pressKeyCode(283);//rotate
System.out.println("keycode:283-Rotate");
System.out.println(df.format(new Date())+"\n");
break;
case 4:
//press the key : ROTATE_LEFT
device.pressKeyCode(24);//left
System.out.println("keycode:24-Left");
System.out.println(df.format(new Date())+"\n");
break;
case 5:
//press the key : ROTATE_RIGHT
device.pressKeyCode(25);//right
System.out.println("keycode:25-Right");
System.out.println(df.format(new Date())+"\n");
break;
default:
break;
}
j = j+1;
}
System.out.println(df.format(new Date())+".testEnd");
}
}
执行结果:
7308times
random=0
keycode:26-Power
2017-11-29 18:45:34
7309times
random=3
keycode:283-Rotate
2017-11-29 18:45:35
7310times
random=1
keycode:282-Fn
2017-11-29 18:45:35
7311times
random=4
keycode:24-Left
2017-11-29 18:45:35
7312times
random=3
keycode:283-Rotate
2017-11-29 18:45:35
7313times
random=4
keycode:24-Left
2017-11-29 18:45:35
7314times
random=4
keycode:24-Left
2017-11-29 18:45:36
7315times
random=0
keycode:26-Power
2017-11-29 18:45:36
7316times
random=0
keycode:26-Power
2017-11-29 18:45:36
7317times
random=4
keycode:24-Left
2017-11-29 18:45:38
7318times
random=1
keycode:282-Fn
2017-11-29 18:45:38
7319times
random=2
INSTRUMENTATION_STATUS: numtests=1
INSTRUMENTATION_STATUS: stream=
Error in testRandomPress:
java.lang.RuntimeException: android.os.DeadSystemException
at android.hardware.input.InputManager.injectInputEvent(InputManager.java:872)
at android.app.UiAutomationConnection.injectInputEvent(UiAutomationConnection.java:129)
at android.app.UiAutomation.injectInputEvent(UiAutomation.java:501)
at com.android.uiautomator.core.UiAutomatorBridge.injectInputEvent(UiAutomatorBridge.java:70)
at com.android.uiautomator.core.InteractionController.injectEventSync(InteractionController.java:655)
at com.android.uiautomator.core.InteractionController.sendKey(InteractionController.java:552)
at com.android.uiautomator.core.UiDevice.pressKeyCode(UiDevice.java:318)
at testlaunch.TestMonkey350.testRandomPress(TestMonkey350.java:66)
at com.android.uiautomator.testrunner.UiAutomatorTestRunner.start(UiAutomatorTestRunner.java:161)
at com.android.uiautomator.testrunner.UiAutomatorTestRunner.run(UiAutomatorTestRunner.java:96)
at com.android.commands.uiautomator.RunTestCommand.run(RunTestCommand.java:91)
at com.android.commands.uiautomator.Launcher.main(Launcher.java:83)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:294)
Caused by: android.os.DeadSystemException
... 21 more
INSTRUMENTATION_STATUS: id=UiAutomatorTestRunner
INSTRUMENTATION_STATUS: test=testRandomPress
INSTRUMENTATION_STATUS: class=testlaunch.TestMonkey350
INSTRUMENTATION_STATUS: stack=java.lang.RuntimeException: android.os.DeadSystemException
at android.hardware.input.InputManager.injectInputEvent(InputManager.java:872)
at android.app.UiAutomationConnection.injectInputEvent(UiAutomationConnection.java:129)
at android.app.UiAutomation.injectInputEvent(UiAutomation.java:501)
at com.android.uiautomator.core.UiAutomatorBridge.injectInputEvent(UiAutomatorBridge.java:70)
at com.android.uiautomator.core.InteractionController.injectEventSync(InteractionController.java:655)
at com.android.uiautomator.core.InteractionController.sendKey(InteractionController.java:552)
at com.android.uiautomator.core.UiDevice.pressKeyCode(UiDevice.java:318)
at testlaunch.TestMonkey350.testRandomPress(TestMonkey350.java:66)
at com.android.uiautomator.testrunner.UiAutomatorTestRunner.start(UiAutomatorTestRunner.java:161)
at com.android.uiautomator.testrunner.UiAutomatorTestRunner.run(UiAutomatorTestRunner.java:96)
at com.android.commands.uiautomator.RunTestCommand.run(RunTestCommand.java:91)
at com.android.commands.uiautomator.Launcher.main(Launcher.java:83)
at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:294)
Caused by: android.os.DeadSystemException
... 21 more
INSTRUMENTATION_STATUS: current=1
INSTRUMENTATION_STATUS_CODE: -1
INSTRUMENTATION_STATUS: stream=
Test results for WatcherResultPrinter=.E
Time: 1976.192
FAILURES!!!
Tests run: 1, Failures: 0, Errors: 1
2.触屏机(GH880)
2.1 eCHat-auto
package testlaunch;
/**
* Author:YangYanXin
* Time:2017.10.26
* Feature:auto--setAccountInfo,Login ,PttCall,auto logout.
**/
import java.io.File;
import java.io.IOException;
import java.sql.Date;
import java.text.SimpleDateFormat;
import android.os.RemoteException;
import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
public class TestSetting extends UiAutomatorTestCase{
public void testInfo() throws UiObjectNotFoundException,RemoteException, IOException{
UiDevice device = UiDevice.getInstance();
System.out.println("device.wakeUp");
device.wakeUp();
UiObject swipe = new UiObject(new UiSelector().resourceId("com.android.systemui:id/notification_stack_scroller"));
System.out.println("swipe.swipeUp");
swipe.swipeUp(10);
sleep(500);
device.pressHome();
sleep(500);
UiObject eChat = new UiObject(new UiSelector().className("android.widget.TextView").instance(20));
assertTrue("eChat is not exists", eChat.exists());
System.out.println("eChat.click");
eChat.click();
sleep(500);
device.click(673, 748);
sleep(500);
File filepath01 = new File("sdcard/auto_screenshot/yyx01.png");
System.out.println("takeScreenshot");
device.takeScreenshot(filepath01);
sleep(500);
UiObject set = new UiObject(new UiSelector().className("android.widget.Button").instance(1));
assertTrue("set is not exists", set.exists());
System.out.println("set.click");
set.click();
sleep(500);
File filepath02 = new File("sdcard/auto_screenshot/yyx02.png");
System.out.println("takeScreenshot");
device.takeScreenshot(filepath02);
sleep(500);
UiObject lms = new UiObject(new UiSelector().resourceId("com.mcptt:id/rlLms_login"));
UiObject lms_checked = new UiObject(new UiSelector().resourceId("com.mcptt:id/lms_checked"));
// UiObject pds = new UiObject(new UiSelector().resourceId("com.mcptt:id/rlPds_login"));
assertTrue("lms is not exists", lms.exists());
// assertTrue("pds is not exists", pds.exists());
System.out.println("lms.click");
lms.click();
UiObject ip = new UiObject(new UiSelector().resourceId("com.mcptt:id/server_ip"));
ip.longClick();
ip.clearTextField();
UiObject port = new UiObject(new UiSelector().resourceId("com.mcptt:id/server_port"));
port.longClick();
port.clearTextField();
if(!lms_checked.exists()){
ip.click();
ip.setText("114.55.174.201");
port.click();
port.setText("13001");
}else{
ip.click();
ip.setText("real.caltta.com.cn");
port.click();
port.setText("80");
}
sleep(500);
File filepath03 = new File("sdcard/auto_screenshot/yyx03.png");
System.out.println("takeScreenshot");
device.takeScreenshot(filepath03);
sleep(500);
UiObject save = new UiObject(new UiSelector().className("android.widget.Button").instance(0));
assertTrue("save is not exists", save.exists());
save.click();
sleep(100);
UiObject user = new UiObject(new UiSelector().resourceId("com.mcptt:id/user_edittext"));
UiObject psw =new UiObject(new UiSelector().resourceId("com.mcptt:id/psw_edittext"));
user.longClick();
user.clearTextField();
sleep(500);
psw.longClick();
psw.clearTextField();
sleep(500);
user.click();
user.setText("19698018079");
sleep(500);
device.click(674, 745);
psw.click();
psw.setText("111111");
sleep(500);
device.click(674, 745);
sleep(500);
File filepath04 = new File("sdcard/auto_screenshot/yyx04.png");
System.out.println("takeScreenshot");
device.takeScreenshot(filepath04);
sleep(500);
UiObject login = new UiObject(new UiSelector().className("android.widget.Button").instance(0));
login.click();
// 截图
// Date a = new Date();
//
// SimpleDateFormat b = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//
// String c = b.format(a);
//
// System.out.println(c);
//
// File files = new File("/mnt/sdcard/aaa/"+c+".png");
//
// getUiDevice().takeScreenshot(files);
sleep(5000);
File filepath05 = new File("sdcard/auto_screenshot/yyx05.png");
System.out.println("takeScreenshot");
device.takeScreenshot(filepath05);
sleep(500);
Runtime.getRuntime().exec("input keyevent --longpress 5 262");
sleep(30000);
System.out.println("PTTcall");
device.pressKeyCode(262);
sleep(1000);
File filepath06 = new File("sdcard/auto_screenshot/yyx06.png");
System.out.println("takeScreenshot");
device.takeScreenshot(filepath06);
sleep(500);
UiObject account = new UiObject(new UiSelector().resourceId("com.mcptt:id/title_left_image"));
account.click();
sleep(500);
UiObject exit = new UiObject(new UiSelector().resourceId("com.mcptt:id/exit_grp"));
exit.click();
sleep(500);
UiObject exitAccount = new UiObject(new UiSelector().resourceId("com.mcptt:id/exit_acc_btn"));
exitAccount.click();
sleep(500);
user.longClick();
user.clearTextField();
sleep(500);
File filepath07 = new File("sdcard/auto_screenshot/JJJ.png");
System.out.println("takeScreenshot");
device.takeScreenshot(filepath07);
sleep(500);
device.pressHome();
}
}
2.2 Test Calendar
package testlaunch;
/*
* Function:test Calendar
* Author:yangyanxin
* Date:2017.11.12
* */
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
Import android.bluetooth.BluetoothClass.Device;
import android.os.RemoteException;
import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
public class TestCalendar extends UiAutomatorTestCase{
public void testCalendar() throws UiObjectNotFoundException, RemoteException{
UiDevice device = UiDevice.getInstance();
UiObject calendar = new UiObject(new UiSelector().className("android.widget.TextView").instance(5));
UiObject spinner = new UiObject(new UiSelector().className("android.widget.Spinner").instance(0));
UiObject listview_item00 = new UiObject(new UiSelector().className("android.widget.LinearLayout").instance(0));
UiObject listview_item01 = new UiObject(new UiSelector().className("android.widget.LinearLayout").instance(1));
UiObject listview_item02 = new UiObject(new UiSelector().className("android.widget.LinearLayout").instance(2));
UiObject listview_item03 = new UiObject(new UiSelector().className("android.widget.LinearLayout").instance(3));
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(df.format(new Date())+"test.begin");
device.wakeUp();
device.pressHome();
calendar.click();
sleep(500);
//进入主界面截图
File calendarPath_month = new File("sdcard/auto_screenshot/"+df.format(new Date())+".png");
device.takeScreenshot(calendarPath_month);
//周历程模式
spinner.click();
listview_item01.click();
sleep(500);
File calendarPath_week = new File("sdcard/auto_screenshot/"+df.format(new Date())+".png");
device.takeScreenshot(calendarPath_week);
//日历程模式
spinner.click();
listview_item02.click();
sleep(500);
File calendarPath_day = new File("sdcard/auto_screenshot/"+df.format(new Date())+".png");
device.takeScreenshot(calendarPath_day);
//日程
spinner.click();
listview_item03.click();
sleep(500);
File calendarPath_date = new File("sdcard/auto_screenshot/"+df.format(new Date())+".png");
device.takeScreenshot(calendarPath_date);
//月历程模式
spinner.click();
listview_item00.click();
sleep(500);
File calendarPath_month01 = new File("sdcard/auto_screenshot/"+df.format(new Date())+".png");
device.takeScreenshot(calendarPath_month01);
device.pressHome();
System.out.println(df.format(new Date())+"test.end");
}
}
3.TestGallery
package testlaunch;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import android.R.integer;
import android.os.RemoteException;
import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
public class TestGallery extends UiAutomatorTestCase{
public void testgallery() throws UiObjectNotFoundException,RemoteException, InterruptedException, IOException{
Runtime.getRuntime().exec("am force-stop com.android.gallery3d").waitFor();
UiDevice device = UiDevice.getInstance();
device.pressHome();
sleep(1000);
for (int i = 0; i < 10; i++) {
Calendar c = Calendar.getInstance();//可以对每个时间域单独修改
int year = c.get(Calendar.YEAR);
int month = c.get(Calendar.MONTH);
int date = c.get(Calendar.DATE);
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
System.out.println(year + "/" + month + "/" + date + " " +hour + ":" +minute + ":" + second);
device.wakeUp();
UiObject swipe = new UiObject(new UiSelector().resourceId("com.android.systemui:id/notification_stack_scroller"));
System.out.println("swipe.swipeUp");
swipe.swipeUp(10);
sleep(500);
device.pressHome();
sleep(1000);
UiObject gallery = new UiObject(new UiSelector().className("android.widget.TextView").instance(8));
gallery.click();
sleep(500);
device.click(140, 311);
sleep(500);
device.click(129, 288);
sleep(500);
device.click(360, 664);
sleep(1500);
UiObject openvideo = new UiObject(new UiSelector().resourceId("android:id/title"));
assertTrue("mode-picture", openvideo.exists());
if(openvideo.exists()){
UiObject openOnce = new UiObject(new UiSelector().resourceId("android:id/button_once"));
openOnce.click();
System.out.println("openOnce.click");
sleep(2000);
File videopath = new File("sdcard/auto_screenshot/"+year +"."+ month +"."+ date + " " +hour + ":" +minute + ":" + second+".png");
device.takeScreenshot(videopath);
sleep(1000);
}else {
File photopath = new File("sdcard/auto_screenshot/"+year +"."+ month +"."+ date + " " +hour + ":" +minute + ":" + second+".png");
device.takeScreenshot(photopath);
sleep(500);
}
Runtime.getRuntime().exec("am force-stop com.android.gallery3d").waitFor();
device.pressHome();
sleep(1000);
}
}
}