• 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软件测试网原创

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

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          • 功能测试框架可以包括:界面友好性测试、功能测试、链接测试、容错测试、稳定性测试、常规性能测试、配置测试、算法测试等等。一、界面友好性测试风格、样式、颜色是否协调;界面布局是否整齐、协调(保证全部显示出来的,尽量不要使用滚动条;界面操作、标题描述是否恰当(描述有歧义、注意是否有错别字);操作是否符合人们的常规习惯(有没有把相似的功能的控件放在一起,方便操作);提示界面是否符合规范(不应该显示英文的cancel、ok,应该显示中文的确定等);界面中各个控件是否对齐;日期控件是否可编辑;日期控件的长度是否合理,以修改时可以把时间全部显示出来为准;查询结果列表列宽是否合理、标签描述是否合理;查询结果列...
            0 0 976
            分享
          • 适合项目:测试任务明确,不会频繁变动;每日构建后的测试验证;比较频繁的回归测试;软件系统界面稳定,变动少;需要在多平台上运行相同测试案例、组合遍历型的测试,大量的重复任务;软件维护周期长。WebDriver API1、定位元素 (定位一组元素为find_elements_)根据ID find_element_by_id()根据Name find_element_by_name()根据Class find_element_by_class_name()根据tag find_element_by_tag_name() tag包括<div> <input>根据连接link例如...
            0 1 1408
            分享
          • 数据分析是从数据中提取有价值信息的过程,过程中需要对数据进行各种处理和归类,只有掌握了正确的数据分类方法和数据处理模式,才能起到事半功倍的效果,以下是数据分析员必备的9种数据分析思维模式:1. 分类分类是一种基本的数据分析方式,数据根据其特点,可将数据对象划分为不同的部分和类型,再进一步分析,能够进一步挖掘事物的本质。2. 回归回归是一种运用广泛的统计分析方法,可以通过规定因变量和自变量来确定变量之间的因果关系,建立回归模型,并根据实测数据来求解模型的各参数,然后评价回归模型是否能够很好的拟合实测数据,如果能够很好的拟合,则可以根据自变量作进一步预测。3. 聚类聚类是根据数据的内在性质将数据分...
            12 12 1223
            分享
          • 1、TCP是互联网中的(1 A)协议,使用(2 C)次握手协议建立连接。当主动发出SYN连接请求后,等待对方回答(3 A)。这种连接的方法可以防止(4 D),TCP使用的流量控制协议是(5 B)。(1)A. 传输层      B. 网络层      C. 会话层      D. 应用层(2)A. 1     B. 2      C. 3     D. 4(3)A. SYN,ACK     ...
            0 0 3783
            分享
          •   通过执行发现,我们在用例03中没有加入fixture,所有他没有执行一些用例的前置和后置操作。  测试报告  unittest:unittest中没有自带的测试报告,需要下载第三方的插件HTMLTestRunner和BeautifulReport来生成详细的测试报告。  pytest:pytest中也没有自带的测试报告,需要下载第三方插件pytest-html或者allure-pytest进行生成详细的测试报告。class Test01:     def test_01(self):     ...
            11 11 827
            分享
      • 51testing软件测试圈微信