功能自动化测试工具技术剖析

文/王东刚

导语:本文基于商业自动化测试工具的研发,分析大量的商业自动化测试工具的实现模式,以提高大家对自动化测试工具本身的认识与理解。

所有的自动化测试工具都由一套非常稳定的、成熟的、持久的测试思想作为支撑,无论QuickTestPro、SilkTest或者 TestComplete这些商业测试工具皆是如此。为何本文标题使用了“结构”一词,而非大家经常朗朗上口的“框架”?我认为,“框架”包容的概念过多,而本文又不打算针对某种自动化测试工具进行整体层面的分析和解构,所以使用了相对描述范围比较小的“结构”一词,用它足矣。

自动化测试的一个经典特征就是对象识别容器,使用对象识别容器内捕获的对象,以实现一系列模拟人工测试的动作,其优点是巨大的,缺陷同样明显存在。对象识别容器内置的识别算法、映射算法、对象排序和出栈算法都让自动化测试工具在执行复杂的、多变的测试案例时,表现的非常耗时和力不从心。另外,对象识别容器因为某些限制,无法知晓它所捕获的对象是临时的还是持久的,所以这直接导致了自动化测试过程难以被跟踪和可重复利用。因此,几乎所有实施了自动化测试的企业,都相应付出了极其昂贵的后续维护成本。

回顾国内测试10年,自动化测试应用的大范围推广是必然的,没有一个行业会停滞不前,总有更好更快的技术来提升整个行业的技术水准。在本文中,我会从一个软件自动化测试工具项目(TestMice)展开,描述一些迅捷的、轻量的自动化测试工具结构,例如对象描述,脚本构建,描述性编程,测试数据驱动,场景恢复,插件和编译运行。我不会评价的任何测试工具的好与坏,我只会说明他们为什么要这么做,为什么在这个领域鱼和熊掌不能兼得。

对象描述

在自动化测试中,对象泛指一切可能被捕获和虚拟的东西,一个按钮就是一个对象,一组文字也是一个对象,一个程序进程也是一个对象。自动化测试工具的核心就是对象识别容器,其他看到的脚本、时序、数据、外调等都是围绕这个核心来开发的。总体而言,针对Win32的对象识别难度要高于对Html对象的识别,主要原因就是早期Win32的体系设计和微软大量未放开的结构与API声明,所以本文使用Win32对象展开一个综述。

Windows操作系统自带“计算器”程序使用了大量的标准Win32控件,针对其对象识别又涉及到一个很著名的单词“句柄”。句柄是个非常泛意的单词,这又要涉及到Windows系统的一系列机制和相关API函数的调用。我这里简单描述一下,窗体(window)诞生首先要向Windows系统进行注册,注册函数为 RegisterClass(API),注册完成后由CreateWindow(API)进行窗体(window)构建,如果这个函数执行成功那么就会返回一个32位的整数值,该值简称为窗体句柄hwnd。窗体句柄值的大小倒是无关紧要,不过它关系着窗体对象,所有需要对窗体(window)进行的操作都必须通过调用它。这个时候,显示屏幕上还不能马上出现窗体(window),接着使用ShowWindow(API) 和UpdateWindow(API)这两个函数后,这个窗体(window)才会最终出现在我们的计算机屏幕上。剩下的就是构建窗体消息循环,在消息回调函数中声明消息处理方式等等,所以Windows也会被称为消息驱动的操作系统。句柄在Windows系统中应用频繁,除了HINSTANCE(实例句柄)、HWND(窗口句柄)、HDC(设备句柄),还有HICON(图标句柄)、HCURSOR(光标句柄)、HBRUSH(刷子句柄)等,正是因为句柄的存在,所以自动化测试工具才能如此顺利的操作窗体对象。在Windows系统里可视对象和部分非可视对象都属于窗体(window)范畴,Button也是,List也是,TextBox等都是,窗体具容器,最终导致整个Windows变得如此“多姿多彩”。下面的工具列出“计算器”程序部分被捕获对象:


  “计算器”程序中包含的对象呈树形结构排列,描述方式为句柄、窗体名称、类名称,通过捕获描述,基本上可以很快的定位到实际窗体对象。商业测试工具会提供很多的可选对象属性,原因就是属性越多,对象识别容器识别对象的精度就越高,负面的是消耗的资源也会相应的增加。有的商业工具采用了“映射”机制,即用户所看到的界面显示对象属性并非是对象识别容器内真正的对象属性,是由测试工具在真实对象上层构建的Object Maps。这样处理的好处是降低了用户理解对象的难度,还可以附加更灵活的搜索模式交与用户选择,例如QuickTestPro提供的“属性正则表达式”、“对象Index或者Location定位”等。另外“对象重定义”技术也是采用了上述原理,将非标准对象在UI层面显示为标准Win32对象,在允许范围内使用句柄、消息对实际对象进行控制。

虚拟化对象技术是由非标准对象识别而诞生,其实操作原理为用户操作鼠标对需要进行虚拟化的有效区域画出一个不可视矩阵。随即,对象识别容器关联父对象,记录虚拟坐标值,通过钩子模拟鼠标和键盘消息可以让原来无法捕获的对象也实现正常的人机交互操作(WEB的虚拟化对象是挂载在对应的Page父对象容器中)。理论上虚拟化对象无上限个数,但是过多的虚拟化对象会影响测试工具的实际执行效率。

在对象的世界里,并存在着测试对象和实时测试对象(也称运行时对象)的概念,有的测试对象在实际软件运行时相关属性会发生变更,或者有些对象在静态捕获的时候根本不存在,它只存在于程序运行时态,那么这些特性多少都会影响对象识别容器的实际识别效果。所有的商业测试工具中都隐含了一些插值对象属性,以最大的灵活度来匹配、捕获和重构对象,这些都作为识别的核心执行机制而被保护起来,相关的技术文献并不多。在 TestMice中,我们也是用了一种特殊的机制来增强运行时对象的识别,尽量做到所见隐藏皆所得,达到识别自动变形的目的。可以说在Win32领域,各家的商业测试工具识别效果都差不多,好在这个Win32时代离普通用户是越来越远了。

在Win32或者Html对象取值中,经常有人问我自动化测试工具是如何实现的。其实只有二种模式而已,第一种即调用对象本身支持的属性通过相应函数获取,例如Win32中使用标准函数getText(API)。第二种就是构建自己的OCR识别程序进行矢量字转换,后者需要投入大量的时间和金钱,当然也可以购买现成的OCR产品,以支持库的方式写入到测试工具中,这些不管是什么测试工具都是支持的。

脚本构建

个人认为,在所有的自动化测试工具中,脚本的组合形式是决定用户是否选用该工具的关键之一,很多测试工具都输在用户对其脚本的第一视觉映象中,其表现形式如下:

Start Main()

SetWindow (“Caption=Scical”,”")

PushButton.Click(“ObjectIndex=3″) //4

PushButton.Click(“ObjectIndex=32″) //+

PushButton.Click(“ObjectIndex=8″) //5

PushButton.Click(“ObjectIndex=27″) //=

Result = Compare (CompareProperties,”Text”,”9.”) //比对结果

End Main

可以看出这是个自动化测试脚本,但是至少让我在短时间内看不出脚本与对象之间的关联,这种脚本设计相对比较失败。有的时候,软件行业充斥着“自己好用,用户就好用”的研发思维,过于突出于技术而忽略了用户体验。如果这些你都可以快速适应,那么就让我再增加一些脚本量:

Start Main()

SetWindow (“Caption=Scical”,”")

PushButton.Click(“ObjectIndex=3″) //46

PushButton.Click(“ObjectIndex=21″)

PushButton.Click(“ObjectIndex=32″) //+

PushButton.Click(“ObjectIndex=8″) //52

PushButton.Click(“ObjectIndex=2″)

PushButton.Click(“ObjectIndex=27″) //=

Result = Compare (CompareProperties, “Text”,”98. “) //比对结果

End Main

上述脚本组成方式,我相信很多实际从事测试行业的人员都是强烈排斥的,这样的脚本越多就会让整个自动化测试项目在重构,分拆和管理上陷入巨大的泥潭,进而加速整个自动化项目的死亡。好的脚本构成应该更贴切用户,让用户能快速观察、快速理解,使用极低的培训成本即可达到快速书写的目的。在TestMice项目中采用一种比较贴切使用者,能快速理解,用极低的培训成本即可达到快速书写测试脚本的方式。并且,这种脚本组成方式便于其维护、分解和重构,例如在对“计算器”程序进行加减乘除运算测试中,TestMice的脚本组成方式为:

Window(“计算器”).Button(“4″).click();

//或者Window(“windowtext=计算器;HWND=234985″).Button(“4″).click();附加属性匹配

//或者Window(“windowtext=^计算*”).Button(“4″).click();附加正则表达式匹配

//或者Window(“算”).Button(“4″).click();模糊匹配

Window(“计算器”).Button(“+”).click();

Window(“计算器”).Button(“5″).click();

Window(“计算器”).Button(“=”).click();

Assert(Window(“计算器”).Edit(“”).getText().trim(“”,”.”),”9″);

上面代码中的“或者”分别代表3种增强脚本模式:附加属性匹配、附加正则表达式匹配、模糊匹配,即用户可以在脚本中以自由添加指定属性的方式来增强对可识别对象的操作能力,其中“模糊匹配”为最高等级的容错识别模式。如果是对多数字进行操作,我们还可以自定义函数包容对象操作,例如:

public static function mockNumberDClick(String number)

{

for(int i=1;i<=len(number);i++)

{

Window(“计算器”).Button(right(number,i,1)).click();

}

}

mockNumberDClick(“20″)

Window(“计算器”).Button(“+”).click();

mockNumberDClick(“20″)

Window(“计算器”).Button(“=”).click();

Assert(Window(“计算器”).Edit(“”).getText().trim(),”40″);

在我们的脚本定义中,需要达到几点要求:第一,脚本与对象的切合要紧密,尽量做到很高的关联可视化。第二,脚本内带规则检查,尽量在第一时间将用户输入性错误排除,节省时间。第三,脚本有常规函数案例显示,并且支持同步对象集合及内嵌线程扩展。第四,脚本有更开放的接口,方便嵌入模式更好的实现。

测试人员既要实现测试动作逻辑,又要特别关注测试数据的选取,如果可以让测试人员从脚本中就快速的获知对象关联,降低他在这块的开发时间,那么这个交互设计就是成功的。简单的设计即可达到最佳用户体验,这也是TestMice项目一直追求的。

描述性编程

如果对自己的对象识别容器有足够的信心,那么支持“描述性编程”是必然的,这些都是经过长时间的测试和升级而最终被确定下来的。为什么Win32中这种技术未被广大用户所知(例如HP Winrunner中即存在描述性编程技术),是因为Win32对象的非标准性和不确定性造成的,大量对象根本无法被捕获,属性的精确获知就更加不可能了,如此大肆推广描述性编程实在是个不明智的商业之举。进入WEB时代后,由于W3C的强力介入让WEB有了一个相对统一的标准,所以描述性编程终于发展起来了。

描述性编程的概念就是要使用者直接脱离传统的识别对象-存储对象-再操作对象的模式,升级为你无需进行这些繁琐的操作,直接使用轻量级的Spy工具查到被测试对象的几个关键属性,即可快速的书写测试脚本以加快开发进度,节省出更多的时间用于调试和脚本重构。一般描述性编程脚本结构为:

对象(“属性:=‘值’”, “属性:=‘值’”, “属性:=‘值’”,… …).动作 “值” //QuickTestPro中使用的描述性编程格式

对象(“属性值1|属性值2|属性值3|… …”).动作(“值”) //SilkTest中使用的描述性编程格式

在TestMice中,这种描述性编程脚本结构微调为:

对象(“属性=值;属性=值;属性=值;… …”).动作(“值”);

描述性编程是对对象多属性复合应用技术的集中表现,在性能上也没有太多的额外耗损,如果用得好还可以节省总测试脚本的执行时间,也更加利于调试。所以,不管采用何种描述性编程格式,本质上是没有任何区别的,可以这样说,描述性编程是自动化测试工具一个必然发展的结果,所有的工具厂商都希望使用者更加关注测试数据和测试业务逻辑,脚本本身的表现形式则是越简单、越直白就越好。

测试数据驱动

数据驱动在自动化测试中至少包含了两种概念,一为对象关键字驱动,二为业务关键字驱动,采用什么样的驱动模式完全取决于使用者本身。下面的表格是对Windows计算器程序进行对象关键字驱动的描述:

Window

Obejct

Description

Action

计算器

SciCalc

Menu

查看,标准型

NULL

Button

CE

Click

Button

1

Click

Button

+

Click

Button

1

Click

Button

=

Click

Edit

NULL

GetText

NULL

Assert

2

Button

CE

Click

对象关键字驱动本质上就是对可操作对象做一个显式列表,然后选择一种数据存储方式(文件,数据库,Excel等)将这些属性、动作存储下来,最后递交给脚本和对象识别容器统一实现。其好处是交互简单明了,坏处是对象变更了整行数据就作废了,造成维护成本偏高,这也是众多商业自动化测试工具的通病。

业务关键字驱动目的性强了一些,所有关键字都跟业务描述和测试数据挂钩,针对这样的业务:

其业务关键字驱动表现形式为:

书   号

会 员 价

数    量

验   证

7111099257

25.50

10

正确

7111255164

78.00

1.5

正确

7111255164

78.00

请输入购买数量

1345266574

49.00

9999

购买数量超过库存数量,请重新输入

3456576574

35.00

0

购买数量输入错误

3456576574

35.00

-1

购买数量输入错误

3456576574

35.00

0.1

购买数量输入错误

对于业务关键字驱动来说,业务本身的数据和对应验证是最为重要的,只有通过更多的测试数据来达到一个满意的、足够安全的测试覆盖率。

场景恢复

自动化测试执行过程中遇到中断事件是非常普遍和令人沮丧的,我们经常告诫测试人员在脚本的执行机器上不要安装过多的额外软件,就是因为某些软件可能产生一些莫名其妙的对话框或者发送一些古怪的消息来阻断正常自动化测试进程。那么作为一款强大的自动化测试工具,保存这种错误场景和继续恢复测试正常执行是非常必要的,这些统称为场景恢复。在场景中,我们需要分析哪些现象会阻断脚本的正常运行?或许是一个软件升级的对话框,或许是来自于网络的一个弹出式窗体,或许是防火墙拦截到的一段信息提示等等,这些一切皆有可能。那么如何处理这些令人讨厌的事情呢?TestMice项目中提供3种场景恢复,即:

l  Windows弹出式对话框场景恢复

l  被测试软件错误场景恢复

l  对象属性变化场景恢复

第一种场景还是需要被动的预先获知弹出式对话的句柄,或者通过预设对话框Title属性,一旦该机制被触发,测试软件会自动寻找符合Title属性的窗体,然后启动由用户选择的4类后续对应处理:

l  模拟鼠标和键盘消息,进行关闭窗体或者其他可定义操作

l  关闭阻碍自动化测试过程的软件进程,可由用户设定此项较危险

l  调用预约脚本,例如发送紧急短信或者邮件通知等

l  重启Windows系统,这里建议自动化测试做在虚拟机里,可用虚拟机SDK作辅助插件开发,保存当前软件执行Cookie

第二种场景在测试过程中是最为常见的,例如已识别菜单项没找到(Winrunner低版本中这种现象比较常见),List下拉项目中数据和脚本预设数据不匹配,出现多个重复的对象(例如打开两个以上相同的IE窗口),或者被测试对象状态为Visable等等,所以针对此场景,最好设置一个All Error处理,即不管出现了什么被测试软件错误,一概调用4类后续对应处理。更好的办法是,错误可以由使用者自定义,那么这就需要测试工具在实现机制上 有更多的考虑了。

第三种场景,对象的属性变化处理最为复杂,不过这种处理机制也是对象识别容器的重要功能,可以由软件内部提供对象重新模糊匹配方式,略过该测试案例 执行,直接跳转到其他待测试案例执行等。同样该场景的触发也可以调用4类后续对应处理。既然测试执行过程中产生阻碍事件是无法预料的,那么有备无患的后续 处理机制就显得格外重要了。

这三种场景恢复TestMice全部支持,并且我们将场景后续处理机制做在了插件里,这样的话就把对其他场景的定义交给了实际使用者,因为我们永远相信,预设的都是不够的,是最容易招惹抱怨的。

插件和编译运行

目前很多测试工具有限的放开了二次开发接口,可以让使用者进行受一定限制的功能开发,但是至今还是没有一款商业测试工具放开到可以操作其对象识别容器的地步。TestMice完全提供操作其对象识别容器内核所用的SDK开发包,以供任何个人或者企业制作具有自己特色的自动化测试应用。另外,我在大量的自动化测试项目实施中发现了一些很有趣的事情,即目前市面上的测试工具脚本大多是以明码方式运行的,企业比较抵触这种方式,认为这种明码执行方式有泄漏企业秘密的重大嫌疑。那么在TestMice项目中有个插件专门负责把脚本编译成二进制模式,顺便对脚本进行优化和执行。插件是所有商业工具扩展的灵魂,开发商如果没有足够的想象力,那么就把这项任务交给广大插件开发者来完成吧。

总结

国内至今尚未有正式机构对自动化测试工具进行整体式的深入研究和商业研发,自2007年国内某人开始制作成体系的研究性自动化测试工具,直至今日发展颇为艰难。在经历了一个又一个的技术攻关后,终于让我们开始成熟起来。项目组同样希望TestMice可以成为国内每个测试人员,不,甚至是每个开发人员手中的测试利器,国产的商业自动化测试工具应用年代终于离我们越来越近了!

关注我们

Join our community of like minded individuals and be the first to hear about products, news and deals.