• 0
  • 0
分享

 背景介绍:

  介绍搜狗输入法按键响应自动化测试的方法及工具,虽然本文中的工具是深度定制化的输入法测试工具,无法应用在其他项目,但相信本文中所使用的Xposed Hook技术、socket多进程通信技术以及自动化思想会对大家有帮助。

  按键响应测试是什么?

  当我们在手机上打一个字时,它大致经历了如下过程:

图1.jpg

  因此,在以上过程中,从用户按下软键盘上的按键到候选词显示的所花费的时间,是衡量输入法性能的一项重要指标。

  按键响应怎么测试?

  按键响应的测试方法有两种:日志方式和视频拍摄方式。

  日志方式:在上述用户按下按键时,程序执行onKeyEvent函数中写日志记下时间戳T1;当开始显示候选词时,程序执行绘制显示函数时写日志记下时间戳T2,通过T2-T1即可获得按键响应的时间。

  优点是:通过日志方式计算,工具成本低;

  缺点是:经过大量的实践对比,函数执行时界面并没有显示,所以这并不能代表用户实际的体验。

  视频拍摄方式:即通过慢速摄像(240帧/S)的录像下,记录从按下按键到显示候选词的过程,进而通过视频拆帧工具进行逐帧的对比。

  优点是:拍摄的内容是用户实际的感受,代表了用户实际的体验;

  缺点是:拍摄视频并拆帧没有形成的工具,成本高,操作繁琐。

  为了得到最接近用户体验的数据,目前搜狗输入法使用的是视频拍摄方式。

  按键响应最大的问题是什么?

  如上述所说,这个测试过程中效率非常慢,我们遇到了最大的两个问题:

  1.如何准确地获得按下按键的时间。

  以前我们的做法是这样的:竖一面镜子,通过镜子的反射来获取手指抬起的时间。如下图:

图2.jpg

  现在我们的做法是这样:

  1).通过XPosed的Hook框架,对用户在手机上按下按键和抬起按键的事件进行Hook;

  2).将Hook到的信息通过Socket发送给浮动窗口;

  3).浮动窗口收到事件消息后,根据按下抬起的时间分别显示绿色和红色,帮助测试人员识别颜色的变化。

  通过hook之后的touch时间处理过程

图3.jpg

  最后的效果如下:

图4.jpg

  2.如何更高效的拆帧获得时间间隔。

  以前我们的做法是这样的:用KMplayer将慢速视频拆帧,然后一帧一帧地人工计算,最后得到T1和T2之间的帧数。

  现在我们的做法是这样:

  1)通过测试工具,对视频进行自动拆帧

  2)基于图像识别的算法,对视频中浮动窗口方框进行识别和输入法候选词识别

  3)通过图像识别得到准确的按下时间T1和候选词显示时间T2,最终得到差值。

  效果如下:

图5.jpg

  代码分享阶段:

  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();
//        }
}
}




作者:搜狗测试 诸葛东明   

来源:51Testing软件测试网原创

  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          • 1、 安装、卸载测试应用程序应能正确安装到设备驱动程序上;能够在安装设备驱动程序上找到应用程序的相应图标;安装路径应能指定;软件安装向导的UI测试;应用是否可以在android不同系统版本上安装(有的系统版本过低,应用不能适配);没有用户的允许, 应用程序不能预先设定自动启动;对于需要通过网络验证之类的安装,在断网情况下尝试一下;安装时空间不足的情况下是否会导致系统崩溃;软件安装过程是否可以取消,点击取消后,写入的文件是否如概要设计说明处理;安装过程被中断(比如来电、短信等)后是否能够继续安装或者导致系统卡顿、崩溃;软件安装过程中意外情况的处理是否符合需求(如死机,重启,断电);卸载是否安全,...
            12 12 1948
            分享
          • 前言性能测试是通过性能的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。负载测试和压力测试都属于性能测试,两者可以结合进行。通过负载测试,确定在各种工作负载下系统的性能,目标是测试当负载逐渐增加时,系统各项性能指标的变化情况。压力测试是通过确定一个系统的瓶颈或者不能接收的性能点,来获得系统能提供的最大服务级别的测试。术语:场景(Scenario):场景即测试场景。在LoadRunner的Controller部件中,可以设计与执行用例的场景,设置场景的步骤主要包括:在Controller中选择虚拟用户脚本、设置虚拟用户数量、配置虚拟用户运行时的行为、选择负载发生器(Lo...
            0 0 1988
            分享
          • 读者提问:测试人员提出的 BUG,要追着开发改吗?阿常回答:不用追。首先要明确,测试是找 BUG 的,而不是负责催 BUG 的,要注意边界。其次要知道,开发其实跟测试一样看重产品的质量,因为他们是真正实施的人,谁不希望自己做的东西能够尽善尽美呢。那么问题来了。既然开发更是希望项目成功,为什么他们还是会遗留一些 BUG 硬是不改呢。别着急,我们先来分析一下可能存在哪些原因导致开发不愿意改 BUG。一、工作流程方面的原因1、有更高优先级的任务,没时间改2、上线时间紧急,开发来不及修改3、开发认为目前实现比产品需求好二、对 BUG 理解不一致1、非常规操作导致的问题2、竞品同样存在的问题3...
            0 0 946
            分享
          •   前言  不知道大家在测试流程中把 “用例评审”放在了什么样的“地位”。在我看来,用例评审是测试流程中不可或缺的一环。于是打算把 我司的用例评审写下来,我们的用例评审是怎么做的,也希望汲取一些其他公司优秀的经验,相互学习下~  用例评审是什么  自我理解:用例写完了之后,不代表这份用例写的都是正确的,场景覆盖是全的,需要在多方人员进行查漏补缺,所以我的理解是:用例评审是产品、开发、测试一起对写好的用例进行一个review的过程。  如果用例都没有评审,直接去执行,可能会存在一些问题。  用例评审参会人员  产品、开发、测试。  详细一点的话,就是 制定该需求的产品,实现该产品的前端开发、后端...
            0 0 1231
            分享
          •   35岁现象和中年危机,是IT乃至所有行业都共同面临的一个问题,就趋势来说,就是由IT/互联网行业开始兴起,然后逐渐扩散到各个行业成为一种社会问题,而究其本质,就是职场上的年龄焦虑带来的“中年危机”。  35岁危机  在这个年龄段,员工往往面临着上有老下有小的生活状态,生活负担和压力最大,而此时的职场竞争力下降,导致职业风险的出现。  具体到35岁现象,则是诸多用人单位将招聘门槛设置为“年龄35周岁以下”, 有的甚至提出“员工90化”。  35岁,本该属于职业黄金期,却成为某些行业的年龄上限,这使得35岁成为了一个非常敏感的年龄,进而衍生出一种弥漫在职场的潜规则:35岁之后,职业之路将不再或...
            1 1 961
            分享
      • 51testing软件测试圈微信