前段时间,参与开发了公司基于appium的UI自动化的工作。写此文章是为了把所用到的技术和思路做一个保留,以便日后查看方便。
PS:1.本文章不涉及服务端代码。2.本文章所涉及的内容只支持Android手机,IOS不在这里说明。
前段时间有幸参与了公司的UI自动化平台的工作, 对于运行自动化case前需要做一系列的准备工作(比如手机的管理工作等),由我来开发完成。因无人请教,所以开始做的时候也无思路,通过Googld或baidu了很多东西,一步一步的从理清思路到开发完成。废话不多说,直接上内容。
本程序主要的功能是在运行自动化case之前,为平台提供一些手机的必要参数、手机状态及与自动化平台的交互功能。目前可承载能力如下:
1:自动识别手机的上线与下线。
2:实时获取手机的状态(用来判断当前手机是否为空闲状态)。
3:获取手机基本参数。
4:http网络协议的引入(用来与服务端进行通信)。
-----------------------------据说有分割线是为了好看???---------------------------------
下面来说下详细的实现步骤:
因工程需要管理手机设备,监听手机上线与下线,而谷歌ddmlib库提供了解决方案。ddmlib是DDMS工具的核心,它封装了一系列对ADB功能的封装,在使用时,需引入ddmlib.jar、ddms.jar和ddmuilib.jar三个jar包,其中ddmlib.jar是核心功能,其它两个是为了查看其实现原理而引的(ddms.jar和ddmuilib.jar包不引入也不会影响其功能的使用)。对于ddmlib库,本工程只使用了很少一部分功能,ddmlib是个很强大的库,能做的事情很多,有兴趣的话可以自行Google其使用攻略。
代码实现:
public void init(boolean clientSupport) {
AndroidDebugBridge.init(clientSupport);
mAdbBridge = AndroidDebugBridge.createBridge("adb", false);
}
要使用ddmlib首先需要初始化AndroidDebugBridge或者说首先要create一个AndroidDebugBridge出来,并且通过getDevices()方法获得了一个设备类(IDevice),这样我们就可以正常的使用ddmlib库里面提供的东西了。
public static void waitDevicesList(AndroidDebugBridge bridge) {
int count = 0;
while (bridge.hasInitialDeviceList() == false) {
try {
Thread.sleep(500);
count++;
} catch (InterruptedException e) {
}
if (count > 60) {
System.err.print("主类等待获取设备超时");
break;
}
}
}
自动识别手机的上线与下线:
识别手机的上线与下线,用到了ddmlib库下的AndroidDebugBridge包(com.android.ddmlib.AndroidDebugBridge)。AndroidDebugBridge.IDeviceChangeListener此接口是可以监听到手机的上线与下线,只需实现此接口中的三个方法即可。deviceConnected(上线),deviceDisconnected(下线),deviceChanged(状态改变)。
@Override
public void deviceConnected(IDevice device) {
this.device = device;
System.out.println("监听到设备ID为 = " + device.getSerialNumber() + "的设备上线");
serialNumberlist.add(device.getSerialNumber());
PCount = serialNumberlist.size();
System.out.println("所有线上手机的ID是 = " + serialNumberlist);
testPcClient.writeFreePhone();
}
deviceConnected:实现deviceConnected()方法后,只要有手机插入就可监听到。上面我们已经获得了一个设备类(IDevice),通过getSerialNumber()就可以获得此设备的devicesID。我这里是只要监听到有设备上线,就add到一个list里方便管理,list.size()就是我当前所有线上手机的个数。
PS:这里与服务端交互的代码删除掉了,关于list的建立只是为了我本地调式方面而使用。当监听到有手机上线时,这时应该调用与服务端规定好的接口,此接口的作用就是用来告送服务器有手机上线了,并且把之前所拿到的手机基本信息一并发送过去(通过adb shell命令来获得手机信息,下面有详细解答),且由服务器记录同时标记此手机为空闲状态。而排重功能也由服务器来做(同一部手机不停的插拔),只要我这里监听到有上线,就会请求此接口。
@Override
public void deviceDisconnected(IDevice device) {
this.device = device;
System.out.println("监听到设备ID为 = " + device.getSerialNumber() + "的设备下线");
serialNumberlist.remove(device.getSerialNumber());
PCount = serialNumberlist.size();
testPcClient.writeFreePhone();
}
deviceDisconnected:实现deviceDisconnected()方法后,就可以监听到手机的下线,原理同手机上线。
PS:同样,这里也需调用与服务器规定好的接口,来通知服务器哪台手机下线,并由服务器更新当前手机状态。
@Override
public void deviceChanged(IDevice device, int changeMask) {
if (device.isOnline()) {
System.out.println("Device change online " + device.getSerialNumber());
} else {
System.out.println("Device change offline " + device.getSerialNumber());
}
}
deviceChanged:可以监听到手机状态改变的接口。
实现了上面的三个接口后,我们就可以实时的获取到当前手机的个数以及每个手机devicesID,为自动分配手机打下了基础。为了使代码清晰简洁,以及使用便利的考虑,我们需要对AndroidDebugBridge类做一次封装,封装其初始化、注册监听器、移除监听器等等一系列方法。
// 注册设备监听器
public void addDeviceChangeListener(
AndroidDebugBridge.IDeviceChangeListener listener) {
AndroidDebugBridge.addDeviceChangeListener(listener);
}
// 移除监听器
public void removeDeviceChangeListener(
AndroidDebugBridge.IDeviceChangeListener listener) {
AndroidDebugBridge.removeDeviceChangeListener(listener);
}
最后新建DeviceManager设备管理类(单例模式),通过该类可启动服务,注册监听器等。
public static DeviceManager getInstance() {
if (INSTANCE == null) {
INSTANCE = new DeviceManager();
}
return INSTANCE;
}
// 启动
public void start() {
androidDebugBridgeWrapper = new AndroidDebugBridgeWrapper();
deviceChangeListener = new DeviceChangeListener();
androidDebugBridgeWrapper.addDeviceChangeListener(deviceChangeListener);
androidDebugBridgeWrapper.init(false);
}
// 销毁
public void destory() {
if (androidDebugBridgeWrapper == null) {
return;
}
androidDebugBridgeWrapper.removeDeviceChangeListener(deviceChangeListener);
androidDebugBridgeWrapper.terminate();
}
至此识别手机的上线与下线基本完成了。
PS:关于ddmilb的使用,是我在网上借鉴了某位大神的文章,可惜写这篇文章的时间过久,大神的文章已经找不到了。。。
实时获取手机的状态(判断手机是否为空闲状态)
实时获取手机状态的目的是为了当自动化case运行的时候,可以智能的分配当前空闲状态的手机。对于自动化而言,我定义的空闲状态为当前所有未运行自动化case的手机,全部都是空闲状态的手机。运行自动化case的时候,不管你是使用appium,robotium或是monkey等等的这些框架,都会在某个app里面运行,所以我的做法查看当前手机是否在运行滴滴的app,有则是非空闲,无则是空闲。
PS:还有一种做法是可以监听自动化框架的进程,比如监听appium是否运行,原理与监听app程序是一样的。
public static String getPhoneProcess(String devName, String packageName) {
Process process = null;
String buff = "";
String processName = null;
try {
process = Runtime.getRuntime().exec(
"adb -s " + devName + " shell ps | grep " + packageName);
BufferedReader br = new BufferedReader(new InputStreamReader(
process.getInputStream(), "utf-8"));
while ((buff = br.readLine()) != null) {
System.out.println("我是手机:" + devName + "我有xx进程" + buff);
processName = buff;
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return processName;
}
这里直接用的adb shell命令来获取,devName是当前手机的devicesID,packageName是你要查看app的包名。在上面我们已经把所有在线手机放到了一个list里面了,而这个list存的都是我们当前手机的devicesID,这里我们需要取出全部的devicesID,来检查哪些手机有xx的进程,并且每N秒会轮询一次,反复不断的检查当前手机的状态是否已经变更。
获取手机的基本参数
不管是在appium运行时还是平台化都需要当前运行手机的一些参数。尤其是当你想做成平台化时,咱们的测试人员就可以在平台上看到当前都有哪些手机在线,哪些是空闲状态且可以通过平台来执行自动化case,所以就要知道这些手机都是什么型号,什么品牌等一些的手机基本信息。
我这里同样使用到了adb shell命令来获取,比如:获取手机系统版本号、 获取手机厂商、获取手机型号、获取手机分辨率等。
// 获取手机屏幕分辨率
public String getScreen(String devName) {
process = null;
String buff = "";
try {
process = Runtime.getRuntime().exec(
"adb -s " + devName + " -d shell wm size");
BufferedReader br = new BufferedReader(new InputStreamReader(
process.getInputStream(), "utf-8"));
while ((buff = br.readLine()) != null) {
System.out.println("手机" + devName + "的分辨率是:" + buff);
return buff;
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// 获取手机系统版本号
public String getSystemVersion(String devName) {
process = null;
String buff = "";
try {
process = Runtime.getRuntime().exec(
"adb -s " + devName
+ " shell getprop ro.build.version.release");
BufferedReader br = new BufferedReader(new InputStreamReader(
process.getInputStream(), "utf-8"));
while ((buff = br.readLine()) != null) {
System.out.println("手机" + devName + "的系统版本是:" + buff);
return buff;
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// 获取手机厂商
public String getModel(String devName) {
process = null;
String buff = "";
try {
process = Runtime.getRuntime().exec(
"adb -s " + devName + " -d shell getprop ro.product.model");
BufferedReader br = new BufferedReader(new InputStreamReader(
process.getInputStream(), "utf-8"));
while ((buff = br.readLine()) != null) {
System.out.println("手机" + devName + "的厂商是:" + buff);
return buff;
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
// 获取手机型号
public String getBrand(String devName) {
process = null;
String buff = "";
try {
process = Runtime.getRuntime().exec(
"adb -s " + devName + " -d shell getprop ro.product.brand");
BufferedReader br = new BufferedReader(new InputStreamReader(
process.getInputStream(), "utf-8"));
while ((buff = br.readLine()) != null) {
System.out.println("手机" + devName + "的型号是:" + buff);
return buff;
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
关于获取手机信息就不一一帖出代码了,反正adb shell是很强大的,根据自己的项目使用就好了。
http网络协议的引入(用来与服务端进行通信)。
这部分内容不做重点交代,我使用的是okhttp框架,关于这个框架的攻略网上一大把。。
这里我只说下关于手机管理的接口思路,个人认为比较重要的接口至少要有下面这3个需要跟服务器的同学协商定义。
uploadxxxxxxData:当监听到有手机进入时,会调用此接口。此接口的作用就是用来告送服务器有手机上线了,并且把之前所拿到的手机基本信息一并发送过去,且由服务器记录同时标记此手机为空闲状态。而排重功能由服务器来做,也就是同一部手机不停的插拔,服务器不能重复记录。
updatexxxxStates:此接口是用来更新手机的状态,情况一:当有手机下线时,调用此接口,通知服务器当前手机离线。情况二:当我监听到有手机在运行自动化case时,调用此接口,通知服务器当前手机为非空闲状态。情况三:当自动化执行完毕时,调用此接口,通知服务器当前手机为空闲状态。
getxxxxxxxxxData:这个接口简单的说就是从服务器获取要运行自动化case的手机。通过上面2个接口,服务器已经知道当前有多少手机在线,有多少是空闲状态,当我运行自动化case的时候,服务器要下发其中的一部或者几部空闲的手机给我。这里服务要注意并发的情况,比如自动化这边要同时运行3个自动化case,服务就要下发不同devicesID的手机给我。
以上就是本人认为运行自动化case前该做的工作,因无人指导完全靠自己摸索,也希望能看到此贴的大神们提出不同意见,或是你有别的思路想法,并且也应用到所在项目中的话,非常乐意大神们留言,咱们可以一起探讨探讨。