如何建立自己的轻量级 UI 测试工具
注:本文只做讨论,以 抛砖引玉为主,其中不对的地方希望大家指正。
目前流行的自动化测试 工具很多,如 WR 、 QTP 、 ROBOT 、 ROBOTJ 、 TC 、 RFT 可能一下子都数不过来,功能很强大,有没有想过做一个属于自己的测试工具?应该还有很有意思的。
开门见山,先从这类测试工具的 基本原理说起,向大家展示自动化测试工具是 不是像大家想的那样神秘,它到底是如何工作的,最后探讨一下如何建立自己的测试工具。
一 自动化测试工具原理
一个自动化测试工具应该有三大模块:录制、回放和对象 库。就是这三个模块才支持了整个自动化测试工具,下来分别探讨下这三个模块:
一 录制( Recorded )
录制就是把操作的过程记录下来,在这个过程中需要记录的主要有以下三点 :鼠标事件、键盘事件、事件响应过程的对象。对象即窗体( WINDOWS 系统 中,控件就是一种特殊的窗体,所以本文中不再区分窗体和控件,一律叫窗体),这些对象会被存入一个叫对象库的地方,当然和对象相关的属性也会被记录如 class 、 caption 、 enabled 等等就会和对象一起存入对象库,并分配一个唯一的 ID 进行识别。
在这个过程用到的技术是一种叫做“钩子( hook )”的东西,在你点击录制后,测试工具会加载一些相关的钩子,如鼠标钩子,键盘钩子就是最常见的两种, WINDOWS 的钩子函数可以认为是 WINDOWS 的主要特性之一。利用它们,您可以捕捉您自己进程或其它进程发生的事件。通过 “ 钩挂 ” ,您可以给 WINDOWS 一个处理或过滤事件的回调函数,该函数也叫做 “ 钩子函数 ” ,当每次发生您感兴趣的事件时, WINDOWS 都将调用该函数。一共有两种类型的钩子:局部的和远程的。局部钩子仅钩挂您自己进程的事件。远程的钩子还可以将钩挂其它进程发生的事件。当您创建一个钩子时, WINDOWS 会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。需要注意的是如果是一个远程钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用远程钩子,就必须把该钩子函数放到动态链接库中去。
二 回放( PlayBack )
先说一个简单的例子,在使用测试工具的时候,看到一切操作都是自动的,那么点击一个按钮的过程在系统内部是如何完成的? WINDOWS 系统对每一个窗体都有一个唯一的标识,成为句柄(本文中涉及窗体句柄),句柄是和对象一一对应的 32 位无符号整数值。对象可以映射到唯一的句柄,句柄也可以映射到唯一的对象。 WINDOWS 众多的 API 当中就是如何通过句柄得到和这个句柄相关的窗体的坐标位置,然后移动鼠标到这个位置,再产生一个鼠标点击的事件就完成了一个单击的回放。那是如何得到句柄的?不错在回放的过程中得到句柄是最重要的,这又要提起对象库,通过对象库里记录的每个对象的相关属性找到正确的窗体,这个就是在录制过程中(或者在添加对象的过程)需要完成的事情。
三 对象库( Object Map )
对象库是测试工具开发商自己定义的一种数据结构,每个窗体在对象库中存为一个对象,对象包括自身许多的属性。目的在于回放的时候告知程序应该去操作什么样的控件,就是“重现”操作环境的目的。
二 做自己的测试工具
接下来就看怎么做自己的功能回归测试工具,设计的框架如下:
这个框架大致分为四大部分, SPY++ 部分用来做对象的管理添、删、修改等, Object Map 是自定义的伪对象库, Run 进行运行操作过程,运行的过程应该使用多线程运行的机制,大致为三个线程:
1)
负责运行过程。
2)
处理运行过程中的意外窗体的弹出。
3)
负责记录日志和生成报告
接下来要做的事情就是先定义对象库和运行时的“脚本”结构。只取几个属性就好,对象库定义为一个 win.obj 文件,结构如:
其实就是一个 xml 文件,当中的 ID 就是用来唯一标识此对象的,这样不致于在回放的过程找到不正确的或者相似窗体。这样在录制的过程中通过 SetWindowHookEx 来加载鼠标钩子和键盘钩子记录操作的过程,比如在鼠标钩子中可以知道什么时候鼠标产生了什么事件,比如一次单击,那么鼠标钩子就通过单击消息的 lParam 的参数中取出鼠标所单击的坐标,然后通过 GetWindowFromPoint 得到止坐标下窗体的句柄,再使用 GetClassName , GetWindowText , GetWindowRect 等相关函数取得对应的属性的值,存入对象库。
如何回放?回放需要有一个像商业测试工具一样的脚本才能回放啊,那样比较复杂,使用一种最简单的办法,再自定义一种“脚本”来实现回放的功能,脚本文件为: run.script 。格式如下:
其实还 是 xml 。在这个文件中先指定运行时需要的对象库,每一个 run 代表一个动作, ID 是和对象库中的 Run 一对应的,就是说在运行时,先用 ID 在对象库中进行匹配,匹配到后把对象的相关的属性拿出来,如 :Class , Caption 等再通过 Findwindow 和 FindWindowEx 来取属性一样的窗体的句柄。
在上面的东西定义好后,就开始着手设计代码中类的设计,如下:
主要分为四个大类, GuiTesObj 里封装一些常用的对象操作如:单击、双击、关闭激活窗体等,可以对它进行继承从而完成对各种窗体的操作, WinInfo 存放对象的相关属性, BuildMap 里封装生成对象库的过程, AutoRun 进行运行的过程。
在写代码的过程中需要解释以下几点:
1) 定位窗体
不能直接使用 WINDOWS 的 API ,比如 FindWindow 这个函数它只是在一个瞬间完成对窗体的操作,而商业的测试工具却能对一个窗体进行一段时间的查找,所以可以做如下的封装:
对于查找二级窗体的方法也同上进行封装。另一个很值得关注的问题如果界面上有两个一模一样的窗体怎么办?还是没有 CAPTION 的,比如登陆界面的用户名和密码的输入框,应该如何区别?在 WINDOWS 系统中,每一个窗体在构造的时候对它所包含的所有窗体是有顺序的,这个顺序叫 INDEX ,窗体自身的 INDEX 永远为0,最先构造的窗体对象为 1 ,依次排序。
2 )操作
在得到一个窗体的句柄之后,就可对其进行相关的操作,具体是通过句柄得到此窗体坐标,再移动鼠标到这个位置,发生一个鼠标事件、键盘事件或者其它的操作。以鼠标单击为例:
需要注意的是在这个函数开头用了一个 SetFocus 激活目标窗体,再进行事件操作,防止出现目标窗体被其它窗体挡住的现象。
3) 生成对象库
生成对象库的过程就是在加载“钩子”后,把有进行过操作的窗体的相关属性按照之前定义好的格式记录入对象库,对象库的格式就是一个树形结构的数据类型,完全按照窗体对象的层次关系写入对象库,每个对象必须有唯一的 ID 进行标识,以防止出现在回放的过程中出现对象定位错误的现象。这里需要用到的函数如: EnumWindowsProc, EnumChildProc, GetWindow 等和获取对象属性的函数: GetClassName, GetWindowText, GetWindowRect等。
4) 运行的“脚本”
在解析我们自己定义的脚本结构时需要对操作的衔接、对象 ID 在两个文件中的正确匹配、和控制回放都要考虑清楚,因为这个过程是整个工具最重要的中枢,它负责把所有的东西都连接起来。这部分代码是写入在 AutoRun 的类中的。
5) 容错的处理
在脚本回放的过程常会有异常窗体的弹出或者目标对象迟迟没有出现,可能还会有很多想不到的意外情况总是在影响着脚本的回放的正确性和健壮性,因为运行的过程会再起一个线程专门进行容错的处理,对一些异常的窗体一般都进行关闭操作,以防止造成脚本运行中断。
以上工作都做完之后,在主函数中进行如下调用:
再加上这句:
#pragma comment (linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
将进程隐藏掉(看起来就更专业了 ^_^ )。
到这里所有的工作都做完了,再回顾一下工具使用的整个流程,先添加对象至对象库,再编辑“脚本”如前面所附“脚本”的图,其中这幅图里的过程是操作 WINDOWS 计算器完成 1+2+...+9+10 的计算,然后再给计算器的输出框写入字符串“运行结束”以标记运行完成,运行一下马上就给看到结果,按照上面做的这个脚本运行后的结果,截图为证:
前面那幅是在运行到点击7、 + 后的时候,后面那幅是运行结束后的。这就是一个单击完成的整个过程,其它的操作都类似。还差的一个就是“脚本”的编辑界面,做成这个,就有了一个属于自己最简单的功能回归测试工具。
[ 后记 ] :本文只是简单的和大家探讨了一下自动化测试工具的实现原理和一个简化的不能再简化的轻量级自动化测试工具建立,如果要做一个可以应用的的测试工具这些是远远不够的,所以希望通过此文能让大家对流行的商业工具有一个和平常不一样的认识,更好的理解、认识、使用这些工具,为做好日常的工作服务。
作者:GangWang
出处:http://www.cnblogs.com/GnagWang/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。