基于Hook和图像识别的自动化技术

发表于:2016-10-19 11:19

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:搜狗测试 诸葛东明    来源:51Testing软件测试网原创

  背景介绍:
  介绍搜狗输入法按键响应自动化测试的方法及工具,虽然本文中的工具是深度定制化的输入法测试工具,无法应用在其他项目,但相信本文中所使用的Xposed Hook技术、socket多进程通信技术以及自动化思想会对大家有帮助。
  按键响应测试是什么
  当我们在手机上打一个字时,它大致经历了如下过程:
  因此,在以上过程中,从用户按下软键盘上的按键到候选词显示的所花费的时间,是衡量输入法性能的一项重要指标。
  按键响应怎么测试?
  按键响应的测试方法有两种:日志方式和视频拍摄方式。
  日志方式:在上述用户按下按键时,程序执行onKeyEvent函数中写日志记下时间戳T1;当开始显示候选词时,程序执行绘制显示函数时写日志记下时间戳T2,通过T2-T1即可获得按键响应的时间。
  优点是:通过日志方式计算,工具成本低;
  缺点是:经过大量的实践对比,函数执行时界面并没有显示,所以这并不能代表用户实际的体验。
  视频拍摄方式:即通过慢速摄像(240帧/S)的录像下,记录从按下按键到显示候选词的过程,进而通过视频拆帧工具进行逐帧的对比。
  优点是:拍摄的内容是用户实际的感受,代表了用户实际的体验;
  缺点是:拍摄视频并拆帧没有形成的工具,成本高,操作繁琐。
  为了得到最接近用户体验的数据,目前搜狗输入法使用的是视频拍摄方式。
  按键响应最大的问题是什么?
  如上述所说,这个测试过程中效率非常慢,我们遇到了最大的两个问题:
  1.如何准确地获得按下按键的时间。
  以前我们的做法是这样的:竖一面镜子,通过镜子的反射来获取手指抬起的时间。如下图:
  现在我们的做法是这样:
  1).通过XPosed的Hook框架,对用户在手机上按下按键和抬起按键的事件进行Hook;
  2).将Hook到的信息通过Socket发送给浮动窗口;
  3).浮动窗口收到事件消息后,根据按下抬起的时间分别显示绿色和红色,帮助测试人员识别颜色的变化。
  通过hook之后的touch时间处理过程
  最后的效果如下:
  2.如何更高效的拆帧获得时间间隔。
  以前我们的做法是这样的:用KMplayer将慢速视频拆帧,然后一帧一帧地人工计算,最后得到T1和T2之间的帧数。
  现在我们的做法是这样:
  1)通过测试工具,对视频进行自动拆帧
  2)基于图像识别的算法,对视频中浮动窗口方框进行识别和输入法候选词识别
  3)通过图像识别得到准确的按下时间T1和候选词显示时间T2,最终得到差值。
  效果如下:
  代码分享阶段:
  Xposed Hook的代码实现:
findAndHookMethod("com.android.server.wm.PointerEventDispatcher",null,
"onInputEvent",
InputEvent.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
//XposedBridge.log( "com.android.server.wm.PointerEventDispatcher Hook Success!");
MotionEvent inputEvent = (MotionEvent) param.args[0];
int k = inputEvent.getAction();
DecimalFormat df = new DecimalFormat("0.00");
float x = inputEvent.getX();
float y = inputEvent.getY();
SocketAddress address = new InetSocketAddress("127.0.0.1", 8803);
switch (k & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
XposedBridge.log("ACTION_DOWN " + k + " x=" + df.format(x) + "  y=" + df.format(y));
try {
String string = "ACTION_DOWN";
byte[] data = string.getBytes();
DatagramPacket mPacket = new DatagramPacket(data, data.length,address);
DatagramSocket mSocket = new DatagramSocket();
mSocket.send(mPacket);
mSocket.close();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case MotionEvent.ACTION_UP:
XposedBridge.log("ACTION_UP " + k + " x=" + df.format(x) + "  y=" + df.format(y));
try {
String string = "ACTION_UP";
byte[] data = string.getBytes();
DatagramPacket mPacket = new DatagramPacket(data, data.length,address);
DatagramSocket mSocket = new DatagramSocket();
mSocket.send(mPacket);
mSocket.close();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case MotionEvent.ACTION_POINTER_UP:
XposedBridge.log("ACTION_POINTER_UP " + k + " x=" + df.format(x) + "  y=" + df.format(y));
break;
case MotionEvent.ACTION_POINTER_DOWN:
XposedBridge.log("ACTION_POINTER_DOWN " + k + " x=" + df.format(x) + "  y=" + df.format(y));
break;
}
super.beforeHookedMethod(param);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable{
super.afterHookedMethod(param);
}
});
}
public class UDPServer extends Thread{
private static final String MSG_TAG = "UDPServer";
@Override
public void run() {
try {
byte[] buffer = new byte[20];
DatagramSocket mServerSocket = new DatagramSocket(8803);
DatagramPacket mPacket = new DatagramPacket(buffer, buffer.length);
while (true) {
mServerSocket.receive(mPacket);
Log.d(MSG_TAG , "接收到的长度:" + mPacket.getLength() );
Log.d(MSG_TAG , "端口地址:" + mPacket.getAddress().getHostAddress() );
String strRead = new String(mPacket.getData()).trim();
Log.d(MSG_TAG , "内容:" + strRead );
Looper curLooper = Looper.myLooper ();
Looper mainLooper = Looper.getMainLooper ();
String msg = "" ;
if (curLooper== null ){
myHandler = new MyHandler(mainLooper);
} else {
myHandler = new MyHandler(curLooper);
}
if (strRead.contains("ACTION_UP")){
msg = "ACTION_UP";
}else if(strRead.contains("ACTION_DOWN")){
msg = "ACTION_DOWN";
}else {
msg = "NO ACTION";
}
myHandler.removeMessages(0);
Message m = myHandler.obtainMessage(1, 1, 1, msg);
myHandler.sendMessage(m);
}
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//        } catch (ClassNotFoundException e) {
//            // TODO Auto-generated catch block
//            e.printStackTrace();
//        }
}
}
  图像识别代码实现:(略)
版权声明:本文出自《51测试天地》原创测试文章系列(四十三)投稿。51Testing软件测试网及相关内容提供者拥有51testing.com内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像,否则将追究法律责任。
相关推荐:
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号