关于作者

keqq.jpg
陈霁网络ID云层,全栈测试、TestOps测试运维推动者;DevOps、敏捷测试资深专家,霁晦科技创始人、Viptest联合创始人。TID&NJSD&ISQE&NCTS&GITC分享嘉宾、著有《性能测试进阶指南-loadrunner9.1实战》、《性能测试进阶指南-Loadrunner11实战》系列、《LoadRunner12七天速成宝典》。为行业内数百家公司数万学员提供测试技术咨询、培训、服务。
DevOps Master、PMI-ACP、Agile Scrum Master证书获得者、DevOps Foundation国内首批认证讲师,TMMI会员。《测试敏捷化白皮书》项目经理、阿里云效(DevOps)平台认证架构师,阿里大学DevOps认证开发负责人。
在百度阅读上著有《接口测试最佳实践》《LoadRunner11七天速成宝典》《小白成长建议:软件测试入门》《QTP最佳实践》《WebDriver从入门到到进阶》等作品,超过20万读者。

前言

虽然HP QuickTest Professional(后简称QTP)好像是一个有点过时的东西,在Selenium和Robotium甚至Renorax的围剿下已经销声匿迹了,但是对于一个小白来说QTP真是一个非常好的入门工具,从基本语法到自动化的对象识别原理,再到框架设计的思想,QTP成为了简单而实用的参考。关键字视图及数据驱动模式,各种断言函数库,活脱就是一个完整的自动化框架,而且VBS的语法对于没有任何编程基础的小白来说是最合适不过的了。
所以对于一个彻底的小白希望进入自动化那么先从QTP开始熟悉整个基本编程和对象实现原理吧,接着切换到JAVA/Python的WebDriver体系,最终拿下Robotium和Appium,从而通关自动化基础。
自动化进阶?无非就是更好的设计来实现架构了。

本教材配对视频地址:

https://www.bilibili.com/video/av91528306

RPA

RPA,即Robotic Process Automation(软件流程自动化)在今年突然开始流行,而QTP/UFT正是RPA的一个非常好的解决方案,所以对于期望了解这块自动化的朋友可以参考本书,UFT的基本操作和QTP完全一致。

到底什么是自动化?

几乎所有的自动化书籍都会长篇大论的介绍各种理论只是,不能说没用但是总觉得啰嗦,个人的感觉无非是“自动化就是把手工的变成自动执行而已!”,为什么而要做自动化无非就是“ROI投资回报比”。
可以这样说,自动化的概念本质上是非常广的,而小白往往觉得是一个工具的问题,比如WebDriver,而是实际上是一个流程,从而一叶障目不见泰山。
好比电饭煲,它就是一个烧饭的自动化工具,但是它并不是整个餐饮的完整自动化链,如果算上自动加米,自动加水,自动煮饭这些在一起才可以勉强算一小块自动化。而上面的电饭煲无非就是一个类似WebDriver的工具,它可以实现自动化中的关键一步,但是并不是全部,而且独立的看待它,其实用处也不大。

QTP的安装

从多个原因角度来说,个人推荐大家在Win7的64位系统下安装QTP11版本(不要使用汉化版本),而本书也是基于这个版本和环境来进行说明的,其中注意IE的版本为8.0,并且安装了支持64位的相关补丁。如果使用Win10请安装UFT而不要使用QTP,并且UFT也重置可以无限试用30天。
工具的下载可以参考云层的共享网盘(https://pan.baidu.com/s/1jIHuKKI )。
image.png
安装完成后检查安装版本,推荐安装中选择所有插件。

学霸君_03_QTP11安装.avi (46.47MB)

脚本录制

在开始菜单中找到QTP的启动项。
image.png
点击启动后会看到关于License的说明,默认安装后会有30天的免费试用期(可以无限重置30天试用期)
image.png
点击Continue继续,接着会看到QTP的Add-in Manager插件列表,在安装的时候推荐把所有的插件都安装(不装也没关系),刚开始使用的时候推荐不要勾选任何插件(避免录制到别的一些对象信息)。
image.png
点击OK按钮后就可以进入QTP的主界面了
image.png
接着先来录制一个简单的内容,点击左上角的录制按钮。在弹出的窗体中点击新建一个应用。
image.png
点击+添加被测应用,输入应用名calc.exe
image.png
点击OK确定录制对象,点击确定启动录制,就会发现计算器会自动弹出,然后在计算器上点击1+2=然后关闭应用停止录制。
image.png
然后你会看到出现这样的代码
image.png
个人不推荐使用Keyword View关键字视图模式,所以这里只会使用Expert View专家模式,要多和代码打交道才能有效的理解自动化。
*注意:如果是win10操作系统录制的结果会不同,这个和不同操作系统的不同版本计算器所做的控件属性有关。

回放代码

点击左上角的Run按钮就可以回放代码,首先会跳出结果保存地址信息,点击确定。
image.png
接着会看到计算器会被启动,然后1+2=的操作被重复,计算器被关闭的过程。点击工具栏上的Results按钮查看测试结果(菜单Automation下也有Results项)。
image.png
在测试结果中可以看到本次自动化执行的相关信息。
image.png
如果在选项中配置可以实现脚本报告带截图。

UI自动化的关键-对象识别

在完成了脚本录制和回放后会觉得自动化非常的简单,但是其实自动化困难的地方在于两点:
1.对象识别
2.脚本开发

对象识别的原理

Step1

image.png
上图中有六种形状,然后依次说出这些形状的名字(从左至右,从上往下排列:长方形,直角三角形,五边形,圆形,四边形,菱形)。
如果需要选择菱形,我们就能很快的从上面的六个图形中选择出对应的它。

Step2

现在我给图形标记一个颜色。
image.png
现在我给每个图形都附加了一个属性,这个属性是颜色,但是这个颜色和色块本身的颜色是不匹配的。
这个时候我们要挑出文字是蓝色的色块是不是就会出现一个卡壳的过程了?

Step3

image.png
这次更进一步,在每个形状上还添加了关于这个形状的错误描述文字。
现在我们选择出的红色三角形是不是会更加困难一点了。

对象识别的奥妙

通过上面三个Step可以发现,我们在识别这些东西的时候有两个过程:
1.首先我们脑子里面会有很多对象,我们会把看到的这6个形状和脑子里面的核对一遍,然后给他们标记一个属性,就是这个是什么形状。
2.然后我们听到需求,需要选择什么形状的时候,我们会再用脑子里面的这个形状和图形中的每一个去匹配,最终选出我们要的内容。
但是当被识别对象的属性开始增加之后,我们发现这个图形的属性和我们脑子里面的是不一样的,明明这是红色的但是我们却写蓝色,导致我们无法正确的给第一次标记对象属性一个准确的内容,而第二次对象选择的时候就会比较缓慢的依次匹配,才能找到所需要的对象。当到了Step3的时候,这个问题更加严重,我们需要检查三个属性,效率更加低下。
作为自动化工具在识别对象的过程中也是类似的,无非就以下几个步骤:
1.录制的时候将没见过的对象保存到对象库中保留自己觉得合适的属性
2.在操作对象的时候依次根据对象库属性匹配对象
3.如果匹配唯一那么就对它进行操作,否则提示错误
在知道了这个过程后,我们来看看QTP是怎么实现这个过程最终实现对象识别操作的。

QTP对象库体系-窗体篇

在QTP的主界面左侧有一个关键字视图模式Available keywords,这里列出的Test Objects就是对象库中存在的可以访问的对象。
image.png

这里可以从左侧拖动对象库中的对象到右侧的Expert View中直接转化为代码。
image.png
然后现在的代码就变成了
image.png
如果回放脚本,那么执行的结果就会变成23因为操作为1+22=。

对象别名

这个时候就会发现Button_1这样的名字非常的难于理解,那么接着我们来修改一下对象库中的名称(好比你第一次看到一个人,只知道这个人叫做小王,但是小王这个名字并不是很适合记忆,于是你可以给他换个命名来便于自己记忆)。
选择要改名的对象,右键菜单选择Open Resource打开对象库。
image.png
也可以在菜单Resources下选择Object Repository打开对象库。
image.png
在对象库中,左侧罗列的对象名称,而右侧就是对象的属性列表。
image.png
我们这里先不纠结对象属性为什么是这个样子,我们直接在右侧的Name上修改对应的对象,从而实现对象重命名。
image.png
根据记忆,我们这里依次修改对象的名称和按钮名称对应。修改完成后关闭对象库会发现代码和对象库新的名字对应了,代码的可阅读性上升了。
image.png
*如果大家有Selenium使用基础,这里大家会发现和PageObject体系就很像了,这也是为啥推荐大家先学习QTP的原因之一。

对象属性

接着我们回到对象库上来看看QTP是如何识别对象的,并且保存对象属性的。在对象库中的对象属性中可以看到关于该对象的所有信息。
image.png
在所有属性中Description properties描述属性(基本属性)是我们识别对象最关键的内容。这里对于对象的属性识别有三个(window id、text、nativeclass),并且你发现text属性是没有值的!QTP识别这个按钮1的属性只有两个,一个是window id和nativeclass。简单来说就是按钮1就是一个window id叫做131的并且nativeclass叫做Button的对象。

对象属性扫描

我们在对象库里面能看到对象识别所使用的属性,那么这些属性怎么来的呢?这里就需要使用一个非常有用的工具叫做Object Spy对象间谍。在对象库界面或者主界面上都有这个功能的按钮。
image.png
点击按钮后弹出Object Spy窗口,点击“手型”按钮进入捕获模式(如果对象遮挡可以按住左侧的Ctrl按钮暂停捕获模式,释放Ctrl恢复)。
image.png
在捕获模式下可以选择你要捕获的对象,然后点击左键确认就可以获得该对象的属性列表。
image.png
在Object Spy里面有两个属性列表,一个是Identification另外一个是Native。Identification是QTP能够处理的对象属性,也就是你在对象库里面能够被留下的对象属性;而Native需要在Web模式下的浏览器页面才会出现的源生属性,在后面介绍B/S架构的录制再做介绍。
这里可以看到一个按钮所可以获取的属性非常的多,如何选择合适的属性作为识别对象成为了关键,而QTP提供了一套过滤机制来帮助我们更加简单的录制获取被访问的对象属性。

对象过滤器

打开Tools菜单下的Object Identification,这里就是QTP的对象过滤器。
image.png
左侧的Test Object classes就是对象类型,也就是对象扫描的时候能看到的对象类型,例如常见的Window、WinButton等,其中比较特殊的是WinObject,也就是说QTP认为它是一个Windows对象,但是不属于任何基础标准分类。
这里的右侧写了Mandatory Properties主属性和Assistive Properties辅助属性,QTP会优先使用主属性对对象的属性进行匹配过滤,如果结果不唯一,才会使用辅助属性来识别。
简单举个例子,比如你在某个聚会中要认识一个人(对象类型),那么你优先希望识别他的方式应该是姓名和职业(主属性),但是未必这两个就能唯一识别了,正巧同桌里面也有个叫Kevin的,那么你就需要所在公司、年龄、电话(辅助属性)来帮助唯一识别了。
主属性和辅助属性项都可以自行添加和调整先后顺序,从而定义适合公司对象特点的识别策略。
现在我们就可以来说一下为啥前面的计算器识别出来的对象属性那么奇怪了,先使用Object SPY扫描一下Win7计算器的某一个按钮。
image.png
首先QTP识别到这个按钮是WinButton,然后使用WinButton的方式来识别它,那么对应的主属性和辅助属性分别是nativeclass、text和window id。
image.png
首先看看Nativeclass和text能不能唯一识别这个按钮,在Object SPY中可以看到这个按钮的text属性是空的而Nativeclass的值为Button。
image.png image.png
这里可以发现所有的按钮在计算器上nativeclass都是Button(自己扫描试试,这里就直接总结了)而text属性都是空的,所以这两个属性在一起不能唯一确定某个按钮。接着QTP就开始使用辅助属性了,默认的辅助属性是window id而正好这里是有这个值的,并且每个按钮的值都不同(windows自带的应用都会唯一安排一个window id编号,非windows自带的控件每次启动都会被分配一个很大的随机window id),所以使用window id辅助属性能够唯一识别这个按钮对象,QTP于是就留下了这三个控件属性,这也是为什么对象库里面的按钮是这个样子的原因了。
image.png
那么对于这类按钮能不能简化一下呢,其实只需要把window id作为主属性,别的属性不选择就行了(针对windows7下的计算器定制的识别属性)。这个就好比我们只记忆股票编号,不考虑记忆股票名称。
如果主属性和辅助属性还不能唯一识别对象,那么只能使用下面的Smart Identification智能识别和Ordinal identifier顺序识别(顺序或定位方式)。
image.png
这两种识别方式不太推荐,因为稳定性相对来说较差(从入门的角度来说就不在这里做细节介绍了)。

对象回放失败1

并不是录制好了回放成功了对象库就做好了,有些对象属性会随着时间或者某些特殊的原因发生变化,这也是自动化脚本维护的最大成本,对象识别属性维护。
这里我们录制一个使用Delphi开发的计算器,回放看看会发生什么样的情况。
image.png
启动录制的时候指向要被录制启动的DelphiCalc1.exe,这里还是录制1+2=这样一个操作过程。
image.png
我们可以看到首先点击计算器按钮的操作并没有录制到WinButton按钮对象,而是用WinObject代替了,不过别名还好正好是和按钮名称一样的,还算是比较好阅读,后面还跟着两个数字是坐标。
前面在对象过滤器中简单介绍过WinObject是什么东西,就是一个不属于规范的Window对象,那么识别的属性可以检查一下。
image.png
这里可以看到WinObject识别的属性是非常多的,那么录制得到的WinObject按钮的属性到底是什么呢?我们再看看对象库里面的对应对象。
image.png
发现根据WinObject的主属性和辅助属性识别顺序,QTP使用了regexpwndclass和window id来作为了识别属性,直接回放代码是没啥问题,但是如果关闭了该计算器再次运行的时候就会因为window id的变化导致对象无法被识别了。
image.png
这里可以看到对象1无法找到唯一适配的对象,脚本继续运行提示错误,建议检查对象属性是否和当前显示的界面应用对象属性匹配。这里我们再用Object SPY核对一下按钮1的对应属性。
image.png
可以看到window id已经发生了变化导致无法操作该按钮了。那么针对这类应用怎么办呢?简单来说就是调整识别的属性,不要使用短时间有效的属性,而需要使用长期稳定的属性,接着检查一下该按钮的属性集合寻找比较稳定的属性,可以发现text属性内存放了按钮的名称,而这个属性是几乎唯一并且稳定的属性。
image.png
既然找到了合适的替代属性,那么就有两种做法:
1.手动修改对象库内的对象属性让脚本回放成功。
打开对象库,找到按钮1的对象属性列表,手动添加text属性值为1并且删除window id属性。
image.png
依次将相关对象都调整后,脚本回放成功。
2.手动修改对象过滤器内的WinObject识别属性顺序,让录制成功
打开Object Identification找到WinObject将text设置为辅助属性中优先级最高的属性即可。
image.png
重新新建测试项目录制整个操作过程,对象库对象属性正确使用regexpwndclass和text,脚本回放成功。

对象映射

Object Map可以将一个不确定的对象类型设置为一个比较标准的对象类型,方便操作(QTP会对标准对象提供操作方法)。
在上面的例子中就出现了Delphi的按钮无法被正确的识别成为WinButton的情况,导致还需要调整WinObject对象识别熟悉优先级。有了Object Map后就可以将这类WinObject映射成为一个标准的WinButton,大大简化了录制过程。
打开Object Identification找到User-Defined…
image.png
在弹出的窗口中首先选择待映射对象
image.png
点击手按钮,然后点击要操作的按钮即可(如果出现界面遮挡可以按住左侧Ctrl按钮暂停捕获模式)。
image.png
点击后可以看到Class name获得为TButton,那么在右侧的列表中找到要映射成为的对象类型为Button,点击Add按钮并OK完成对象映射操作。
image.png
添加完成以后,可以使用Object Spy或者直接录制来验证效果,这个时候可以看到TButton按钮已经正确的显示成WinButton了。
image.png
对象映射能解决不少问题但是不是万能的,如果错误的映射对象类型会导致该类对象类型的方法无法正常使用。

对象回放失败2

有些时候对象是完全不能访问的,这个时候如何处理呢?
这里我们录制另外一个计算器,会发现这次连要操作的对象按钮都没有出现,直接出现了窗体访问的脚本。
image.png
进一步检查对象库可以发现,确实没有录制到按钮对象。
image.png
通过SPY可以发现不存在具体对象。
image.png
也就是说这个按钮是不存在的,不是一个Button的控件,而是一个图片或者别的对象。
*眼见的未必是真的,看起来是一个按钮不代表真的是一个按钮,用SPY去仔细分析被测对象是非常重要的,在Selenium中也存在同样的情况,到底是个DIV还是一个Iframe,决定了最后脚本的情况。

虚拟对象

对于没有对象的东西,可以通过虚拟对象的方式来实现。所谓虚拟对象可以理解成将一个区域通过自己的概念定义为一个对象。
选择虚拟对象下的新建虚拟对象
image.png
新建一个Button的按钮虚拟(根据自己要虚拟定义的类型)
image.png
接着选择要Map映射成为按钮的区域,点击Mark Object,在被测对象上选择对应的区域
image.png
这里选择按钮1的区域,标记一个正方形,可以看到被标记了一个宽32高25的方块。
image.png
下一步,将刚才的按钮保存在计算器对象下。
image.png
最后将该对象命名为button1,完成保存。这样我们就设置了一个虚拟对象,来接着重新录制一下代码。
检查对象库可以看到虚拟对象的信息是一个角标和长宽,也就是一个基于主对象的虚拟区域,只要在这个区域中的操作都会被认为是一个虚拟的Button按钮。
image.png
虚拟对象可以很好的帮助我们对一些封装或者非对象进行处理,但是虚拟对象也有一定的缺点,就是只能录制配置,不能自己通过新建或者代码完成,导致后期维护并不是非常方便。

对象回放失败3

QTP对象库体系-浏览器篇

QTP的浏览器对象需要插件支持,重新启动QTP在插件中选择Web。
image.png
启动后,在录制选项中就会出现浏览器录制标签,这里使用IE来进行录制(QTP对IE的版本支持有限请检查补丁和相关版本匹配模式,本文以IE8作为基础)。
*注意:在同一台电脑上安装了QTP和LoadRunner会有一点小冲突,QTP需要IE设置启用第三方扩展插件,而LoadRunner需要取消。
image.png
点击确定启动录制,这里我们录制一个百度搜素“云层 软件测试”关键字的操作。
image.png
可以看到对应的代码如下

  1. Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("wd").Set "云层 软件测试"
  2. Browser("百度一下,你就知道").Page("百度一下,你就知道").WebButton("百度一下").Click

这有前面窗体对象识别的基础上,这里的内容就十分好理解了。

浏览器录制环境

在Web浏览器录制中主要的问题就是录制出来的内容不是Browser对象,而是Window对象。这是由于QTP没有正确的识别到浏览器,从而无法进一步获取浏览器内的元素导致的。
影响QTP录制浏览器的关键是IE加载项的BHOManager,在IE中选择工具菜单下的选项,IE选项中的程序中找到加载项,就可以看到在IE中存在着BHOManager Class这个加载项,这个加载项必须是启动状态才能保证浏览器被识别。
image.png
这也是为什么IE选项中需要启动第三方扩展插件支持的原因,也是对浏览器有限制的原因(新版本有FireFox支持,Chrome支持不详)。
image.png
最后要确保的就是QTP启动的时候选择了Web插件。只要确保BHOManager启动,IE第三方扩展打开,QTP启动Web插件,那么正常情况都能够完整的录制浏览器内容。
检查方式推荐使用Obj Spy在浏览器上扫一下,能看到Web元素就说明正常了。
image.png

对象别名

打开对象库,我们可以发现在Browse浏览器下的对象别名一般都会用网页Title,但是这样会导致脚本内容很长,而且在浏览器中基本上不存在切换浏览器的问题,而多标签也是推荐禁止的,所以这里可以将别名改短。
对于页面别名来说,为了方便管理对象,可以自行定义page页面别名。

对象属性

打开对象库会发现Web的对象库属性是非常特别的,这里我们一个个看一下。
1. Browser对象属性
image.png
在Browser对象上是没有Description properties描述属性的,那么为什么QTP能够识别到浏览器呢?这是因为有个Ordinal identifier的值为CreateTime。也就是说QTP是通过最后被创建窗体时间来判断的,当新建一个浏览器窗体,QTP就会根据浏览器窗体的创建时间,访问最后被创建的哪一个,这也是为什么QTP可以处理弹出窗口但不能处理多标签的原因。
2.Page对象属性
在Page对象属性中的属性就更少了,只有Enable Smart Identification被启动了,也就是Page这个对象是系统智能识别的,智能识别模式就是系统自己尝试选择一些属性来协助唯一。
image.png
3.WebButton对象属性
到了具体的Web对象,对象属性终于比较正常了,可以看到主要的识别还是基于Description properties来实现的。相对HTML代码来说QTP所使用的属性非常的简单,例如这里的百度一下按钮属性为type=submit,name=百度一下,html tag=INPUT,而对应的代码为。
image.png
只要对一下就可以发现针对Web对象的识别非常的简单。除了这种基于HTML属性的识别以外,QTP也支持Xpath模式,在对象库里面添加一个属性。
image.png
Xpath一般被用在各种开源自动化体系中,可以通过Firefox下的firepath插件快速获取。

对象注入

在Web对象中使用Object Spy会看到一个比较特殊的内容。
image.png
除了前面我们一直使用的Identification以外Native也有了内容。Identification可以简单认为是QTP封装属性,而Native就是源生属性。QTP可以通过封装对象来识别对象,进一步操作对象,而源生对象提供了直接注入页面的能力。
首先我们把百度搜索页面上的百度一下按钮添加到对象库,并且拖拽出来形成一个点击按钮的操作。接着修改这个代码,把Click方法修改为Object属性。
image.png
QTP提供了语法提示功能,当打.句号的时候就会提供下拉列表,找到Object属性。接着我们可以直接修改百度一下这个按钮的具体属性,在Object Spy中检查一下这个按钮文字的属性是什么。
image.png
可以看到value属性决定了按钮的名称,那么我们就用注入Native属性的方法来动态修改按钮。
这里通过Object属性下的value属性进行了一个赋值,然后为了看到效果,在下一句代码上写一个wait 10并且点击创建一个断点,让脚本可以在wait 10等待10秒这行上暂停。
image.png
现在我们重新运行这个代码,可以看到按钮的标题已经被修改了。
image.png
其实这里大家也许会觉得很惊讶,但是无需多想,既然页面被下载到了本地再被浏览器解释,QTP能做的也不过是对自己本地的页面进行修改而已,不会影响别人的访问和服务器的内容。
有了源生对象属性,可以非常方便的获取页面的任意元素值,方便自动化的断言。

小结

没错,浏览器篇就这点内容。只要对窗体的对象体系有了基础,切换到浏览器上更为简单,而会出现的对象回放失败也和窗体的原理大同小异,只要大家用心感受QTP的对象识别体系,慢慢会发现其优秀之处。而在以后进一步学习WebDriver或者Robotium等自动化体系时,就能快人一步,迅速掌握。

对象操作与VBS

在有了正确的对象后,剩下的问题其实只有编程了,由于QTP是使用VBScript语法的,这既是它的优点也是它的缺点。
学习QTP及VBS的最大优点就是语法足够的简单,而对应的缺点就是内存容易泄露、扩展比较复杂,这也是为什么把QTP推荐给大家作为入门参考的原因之一,随着熟悉了自动化原理后还是推荐大家转往WebDriver或者Robotium之类的框架上去。
VBS语言能说的东西不多,这里就简单写几个代码就行了,其中会涉及到部分对象操作的内容。
注:Python也有VBS类似的问题,但是从现在来看未来还是很健康的

录制所有应用

现在开始我们不使用QTP启动对象录制了,因为这样的代码维护并不方便,所以录制选项要稍微调整一下。
image.png
现在录制的时候我们选择录制任意窗口,不在强制启动被录制对象了,当然这个时候你点开始菜单啥也会被录制进入脚本,不过在有对象库基础的前提下,大家应该已经能够很清楚的调整对象库内容了。
这里我们就录制一个winxp计算器,操作1+2=得到对应的代码。

  1. Window("计算器").WinButton("1").Click
  2. Window("计算器").WinButton("+").Click
  3. Window("计算器").WinButton("2").Click
  4. Window("计算器").WinButton("=").Click

导入被测对象所有对象

接着通过对象库的Add Objects to Local导入对象模式将整个计算器上的所有对象添加进对象库(演示需要,平常其实不需要把不用的对象都导入到对象库)。
image.png
接着回到代码,现在开始将VBS和QTP的对象进行组合了。VBS是个非常松散的语言,变量其实也不需要强制定义,除非在代码开始处专门写一句Option Explicit。
首先这里先写一个如何将VBS与被操作对象整合的代码。

  1. Dim i
  2. i="1"
  3. Window("计算器").WinButton(i).Click

代码前的Dim i也是可以不写的,这里声明了一个变量i其值为字符串1,在不关闭计算器(不要最小化)的前提下运行一下脚本可以看到按钮1被操作了。也就是说其实WinButton中的对象库别名是可以使用字符串变量替换的。

加载与关闭被测对象

这里为了让后面的代码更方便的运行所以先讲两个函数,打开QTP的Insert菜单下的Step Generator
image.png
这个是QTP的步骤语法生成器,可以生成对象操作及函数操作(内置、外置)
image.png
我们修改Category到Utility Objects中,找到关于启动与关闭程序的对象SystemUtil上。
image.png
可以看到该对象具备Run和Close两个功能,这里就可以通过Run来实现被操作对象的启动,而CloseProcessByName就可以帮助我们来根据进程名关闭进程,避免多个被测对象的同时启动所带来的对象无法唯一识别的问题(好比出现一个双胞胎长得一样,你以前的对象库就无法正确唯一识别了)。
首先先关闭这里的winxp计算器进程再使用Run启动一个,代码如下

  1. SystemUtil.CloseProcessByName "WindowsXp-Calc.exe"
  2. SystemUtil.Run "d:\WindowsXp-Calc.exe"
  3. i=1
  4. Window("计算器").WinButton(i).Click

现在运行这个代码就会首先自动关闭windowsXp-Calc.exe这个进程,然后再去当前目录启动该程序,确保被操作对象的唯一。

VBS常用脚本简介

具体的VBS大家可以自行搜索,这里我就写几个简单的例子大家运行一下就明白了,主要是分支和循环逻辑的基本语法。

  1. 'Dim i 'vbs注释使用单引号
  2. SystemUtil.CloseProcessByName "windowsXP-Calc.exe"
  3. SystemUtil.Run "windowsXP-Calc.exe"
  4. For i=1 to 9
  5. Window("计算器").WinButton(cstr(i)).Click'cstr()函数是把数字转化为字符
  6. Next

上面这个代码是通过循环获得数字1-9,然后转化为字符串作为对象名进行计算器1-9对象的点击操作。

  1. SystemUtil.CloseProcessByName "windowsXP-Calc.exe"
  2. SystemUtil.Run "windowsXP-Calc.exe"
  3. testcase="1+2-3*4/5="
  4. testlen=len(testcase)
  5. x=1
  6. While
  7. x<=testlen
  8. input=mid(testcase,x,1)
  9. print input
  10. Window("计算器").WinButton(cstr(input)).Click
  11. x=x+1
  12. Wend

上面的代码是使用while循环遍历字符串,print语句可以将内容输出到QTP的日志中(不要使用msgbox这样的vbs函数,因为会弹出窗体影响对象识别)。

  1. Dim i
  2. SystemUtil.CloseProcessByName "windowsXP-Calc.exe"
  3. SystemUtil.Run "windowsXP-Calc.exe"
  4. For
  5. i=1 to 9
  6. input=Int((9 * Rnd) + 1)
  7. If input<5
  8. Then
  9. Window("计算器").WinButton(cstr(input)).Click
  10. Window("计算器").WinButton("+").Click
  11. End If
  12. Next

上面的代码随机生成从1-9的数字,分支判断如果随机数小于5则做累加,否则继续循环。
有了这几个例子,大家就会发现通过简单的编程就可以将代码动起来,从而大大简化自动化脚本的复杂度。
小结
自动化脚本是受手工用例中测试步骤影响的,每一条手工测试用例都可能生成一条自动化用例,如何合并复用这些脚本就需要代码的支持。

TestObject测试对象

TestObject(TO)也就是所谓的测试对象,其实本质可以理解为对象库对象,通过其可以完成对对象库对象的操作。从实用性角度来说TO可以用到的情况很少。
对于TO的操作有两大类set和get,共3个方法。

GetToProperty

Gettoproperty的作用是获取对象库属性,简单来说就是提取指定的对象库的某个属性。
现在我的对象库里面有个别名叫做2的按钮,其属性如下
image.png
使用gettoproperty可以获取该对象的属性,代码如下

  1. wid=Window("计算器").WinButton("2").GetTOProperty("window id")
  2. print wid

运行后可以看到结果为132,也就是对象库中该对象window id属性的值。

GetToProperties

和GetToProperty相比GetToPerties的区别就是返回的是该对象的所有属性集合。

  1. set gtps=Window("计算器").WinButton("2").GetTOProperties
  2. print gtps.count
  3. For i=0 to gtps.count-1
  4. print gtps(i).name & "|" & gtps(i).value
  5. Next

由于返回的是一个对象,所以这里需要使用set来存放,对象具备count属性来获取个数,也提供下标来实现对象集合的访问。运行结果会将改对象的所有属性名及对应的值输出。

  1. 3
  2. window id | 132
  3. text |
  4. nativeclass | Button

SetToProperty

与Get不同Set是用来赋值的,也就是说通过该方法可以对对象库中的对象属性进入赋值(临时),从而实现一些比较有用的效果。

  1. Window("计算器").WinButton("2").SetTOProperty "window id","133"
  2. Window("计算器").WinButton("2").click

运行一下这个代码可以发现,我们点击的按钮不是2了,而是3。主要原因就是通过SetToProperty将过去2按钮所对应的window id属性从132修改为了133,而133对应的正好是3这个按钮。
可以发现通过SetToProperty的处理,原本需要很多对象的情况可以被转换为每一类对象只需要一个就行了,然后通过动态替换属性的方式就可以访问,这样做可以大大简化对象库内对象的数量。其实我们人脑也是这样的,只记忆一个人,然后为人替换不同的属性最终实现对于不同人的识别。

  1. For i=131 to 139
  2. Window("计算器").WinButton("2").SetTOProperty "window id",cstr(i)
  3. Window("计算器").WinButton("2").click
  4. Next

通过这样的代码使用一个Button对象就实现了从1-9所有按钮的遍历,对象库中的对象从9个降低为了1个,至于好不好用还要看实际情况了。

小结

可以看到TestObject就是我们对象库中的内容,一旦被保存了一个snapshot快照,那么这个快照就是TestObject了,我们可以通过set或者get来操作和获取该快照的内容,不过这个功能在大多数情况下用处不大,毕竟获取快照的属性是没太大意义的,而设置快照属性还是有些作用的。

RunObject运行对象

RunObject(RO)运行对象,其本质就是动态获取,也可以说Object SPY做的就是RO的事情。
RO对自动化来说的意义非常重大,因为所有的取值、断言都需要通过RO来实现,模糊探索也需要RO来实现,所以这个对象对我们自动化来说非常重要。

GetRoProperty


对于RO的操作就这么一个,不过它的就是所最需要的。通过TO可以来识别对象,而RO就是帮我们了解更多的。
直接来写个例子,终于可以做完整的自动化了。

  1. Window("计算器").WinButton("1").Click
  2. Window("计算器").WinButton("+").Click
  3. Window("计算器").WinButton("2").Click
  4. Window("计算器").WinButton("=").Click
  5. actual=Window("计算器").Static("result").GetROProperty("text")
  6. If actual=3 Then
  7. print "pass"
  8. else
  9. print "fail"
  10. End If
  11. Window("计算器").Close

这个例子就是验证计算结果是不是3,如果是输出pass否则输出fail。在对象库里面多了一个对象就是计算结果的static的框。 image.png
image.png
不知道大家看懂了没有,简单描述就是通过window id来识别计算结果控件,然后通过GetRoProperty来获得这个控件的计算结果,而使用什么属性要通过Object Spy来了解。
image.png
整个过程就和我认识一个人叫做kathy,然后我就问她你多大啦,她就会回答今年18了。认识的原因是因为我有这个人的TestObject,询问年龄就是针对这个TO去获取年龄属性的RO。明年了我再来做同样的事情,她的回答就会更新了,所以RO是动态获取的。通过TO这样的静态属性定位,通过RO来获取定位后的对象属性。

Checkpoint与RO

其实Checkpoint标准检查点就是通过RunObject实现的,而且稳定实用的检查点也就这一种方式,别的扩展检查点都是QTP扩展支持的。
录制模式下检查点是可以使用的,菜单Insert下的Checkpoint提供了多种检查模式。
image.png
其中Standard Checkpoint其实就和我们的RO息息相关。点击新建一个标准检查点,QTP会让你选择被检查的对象。
image.png
这里选择了Static的计算器计算结果作为检查对象,OK确认。
image.png
在Checkpoint属性列表中可以看到QTP列出了该对象的所有属性,并且推荐了两个属性作为检查内容,一个是enabled为True;另外一个是text为0;还有个timeout超时时间,也就是说10秒内判断仍然不匹配的就提示断言(检查点)失败。
点击OK生成下面的代码

  1. Window("计算器").Static("result").Check CheckPoint("0")

代码本身问题的不大,但是维护就比较麻烦了,检查点算作是对象库中的一种特殊类型。
image.png
在对象库中可以看到多了一类Checkpoint and Output Objects类型,里面存放了所有的检查点和输出对象,所以对于维护来说其实麻烦了不少。

Text Checkpoint文本检查点

Text Checkpoint&Text Area Checkpoint两个检查点和标准检查点的主要区别是在于OCR的支持。
image.png
文本检查点在对于被检查对象是只对text属性进行判断的,其中还提供了一些扩展,比如匹配模式等,但是最关键的就是OCR识别。

OCR (Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相机)检查纸上打印的字符,通过检测暗、亮的模式确定其形状,然后用字符识别方法将形状翻译成计算机文字的过程;即,针对印刷体字符,采用光学的方式将纸质文档中的文字转换成为黑白点阵的图像文件,并通过识别软件将图像中的文字转换成文本格式,供文字处理软件进一步编辑加工的技术。

QTP通过这种模式可以帮助我们在某些对象上正确获取文字(比如图片上的文字),但是效果可以勉强接受。OCR的识别是有设置的,在Tools菜单下Options中提供了腿与Text Recognition的选项设置。
image.png
这里最关键的设置就是语言,OCR的识别效率主要是根据配对的语言库来决定的,识别中文就选择ChineseSimplified,另外中文也不需要使用太奇怪的字体,因为识别率本来就不算很高。

Bitmap Checkpoint位图检查点


位图检查点提供了另外一种判断结果是否正确的方式,就是比较原截图和当前运行截图的区别模式。
image.png
在实在没有属性可以匹配的时候可以考虑使用这种模式,主要的问题在于两点:
1.匹配范围,就是截图的区域
2.类似度,因为就算是图片也可能会有微小的区别
通过设置兼容性和选择区域可以实现针对某一块样式的验证,而不是前面某个属性值的验证了,比较适合于针对UI的适配测试。
对于其余几个XML、Database等检查点,这里就不一一介绍了,主要只是针对于数据库中值或者是一个XML结构属性的验证。

Output Value与RO


同样在Checkpoint下还有个Output Value,也就是对象库中存放在同一块的内容。Output Value输出值其实和RO是完全一个概念的,其就是将对象的某个属性通过一种方式存放为变量的手段,和Checkpoint一样提供了好几种方式。

QTP自身提供了存放到一个变量或者DataTable的模式,在Checkpoint中也有同样的支持,只是检查的内容为一个常量还是一个DataTable属性而已,关于这块内容会在后面的参数化中来集中讨论。

小结
GetRoProperty是一个非常有用的方法,通过以识别的属性来定位对象,使用该方法获取所需要的别的属性的值,从而进一步存放变量提供后续使用或者判断该属性的值是否与期望值相同从而完成断言。

无论是Checkpoint还是Output Value都是基于RO的思想甚至是方法来实现的,在我们完成自动化的驱动后,断言是完成自动化最后的一步,在这一步中就提供了该测试用例通过还是失败的结论,通过进一步开发还能扩展缺陷的自动生成及提交别的系统体系,这些都是框架设计的后续内容了。

Reporter对象及结果增强

在默认情况下QTP会自动生成Report报告,但是并不够适合我们自己使用,所以增强结果说明成为了一个必须的步骤。
在使用Checkpoint模式下可以自动根据断言的结果生成对应的结果影响,如果使用RO的自定义模式就不行了,这个时候就需要使用Reporter对象。

Reporter对象

在步骤生成器中找到Utility Objects下的Reporter
image.png


这里我们用的最多的就是ReportEvent来添加一个自定义的报告步骤。
image.png


在EventStatus属性上提供了4个选项:
micDone完成、micFail失败、micPass通过、micWarning提示
ReportStepName是报告中的步骤名称、Details是步骤的详细说明、ImageFilePath是配对的图片地址。
接着我们修改下前面的脚本,使用自定义报告。

  1. Window("计算器").WinButton("1").Click
  2. Window("计算器").WinButton("+").Click
  3. Window("计算器").WinButton("2").Click
  4. Window("计算器").WinButton("=").Click
  5. actual=Window("计算器").Static("result").GetROProperty("text")
  6. If actual=3 Then
  7. Reporter.ReportEvent micPass, "计算结果", "计算结果通过"
  8. else
  9. Reporter.ReportEvent micFail, "计算结果", "计算结果不通过"
  10. End If
  11. Window("计算器").Close

运行完毕查看测试结果报告
image.png


可以看到通过的步骤被定义出来了。

测试步骤截图

测试结果通过这样的报告就行了,但是没有通过的时候就需要详细的记录,最好是有截图。
在对象的操作方法中就提供了截图的功能,比如窗体甚至Desktop桌面。

  1. Window("计算器").WinButton("1").Click
  2. Window("计算器").WinButton("+").Click
  3. Window("计算器").WinButton("2").Click
  4. Window("计算器").WinButton("=").Click
  5. actual=Window("计算器").Static("result").GetROProperty("text")
  6. If actual=3 Then
  7. Reporter.ReportEvent micPass, "计算结果", "计算结果通过"
  8. else
  9. Reporter.ReportEvent micFail, "计算结果", "计算结果不通过"
  10. Desktop.CaptureBitmap "c:\\test1.bmp"
  11. Window("计算器").CaptureBitmap "c:\\test2.bmp"
  12. End If
  13. Window("计算器").Close

上面的代码就做了两个截图,一个是基于桌面的,一个是基于计算器界面的,可以看到C盘根目录出现了这两个文件。
image.png


Desktop.CaptureBitmap是对整个桌面截图,可以全面的看到整个屏幕的情况。
image.png


对象.CaptureBitmap就是对这个对象截图了。
获得对应的截图后要做的是将截图补充到Reporter上,这里还涉及一个截图重名的问题,所以代码中需要增加截图命名的代码。

  1. Window("计算器").WinButton("1").Click
  2. Window("计算器").WinButton("+").Click
  3. Window("计算器").WinButton("2").Click
  4. Window("计算器").WinButton("=").Click
  5. actual=Window("计算器").Static("result").GetROProperty("text")
  6. If actual=3 Then
  7. Reporter.ReportEvent micPass, "计算结果", "计算结果通过"
  8. else
  9. Reporter.ReportEvent micFail, "计算结果", "计算结果不通过"
  10. CurrentTime=year(now)&month(now)&day(now)&hour(now)&minute(now)&second(now)
  11. Bitmapfilename="c:\\"&cstr(CurrentTime)&".bmp"
  12. ' Desktop.CaptureBitmap "c:\\test1.bmp"
  13. Window("计算器").CaptureBitmap Bitmapfilename
  14. End If
  15. Window("计算器").Close

在上面的代码中通过使用当前时间的方式生成截图文件名,并且在Reporter对象上附加这个截图文件名,从而完成报告的截图扩展。
image.png

测试结果说明增强

有了截图了,看起来好像比较完整了,但是在Detail上只有结论没有具体的说明,导致没有明确的错误说明,接着把用例和期望结果实际结果写上去。

  1. expected=2
  2. Window("计算器").WinButton("1").Click
  3. Window("计算器").WinButton("+").Click
  4. Window("计算器").WinButton("2").Click
  5. Window("计算器").WinButton("=").Click
  6. actual=Window("计算器").Static("result").GetROProperty("text")
  7. If actual=expected Then
  8. Reporter.ReportEvent micPass, "计算结果", "计算结果通过"
  9. else
  10. CurrentTime=year(now)&month(now)&day(now)&hour(now)&minute(now)&second(now)
  11. Bitmapfilename="c:\\"&cstr(CurrentTime)&".bmp"
  12. ' Desktop.CaptureBitmap "c:\\test1.bmp"
  13. Window("计算器").CaptureBitmap Bitmapfilename
  14. CaseDetail="计算结果错误! <期望结果为:"&expected&"|实际结果为:"&actual&">"
  15. ' print CaseDetail
  16. Reporter.ReportEvent micFail, "计算结果",CaseDetail,Bitmapfilename
  17. End if
  18. Window("计算器").Close

这样看到的测试报告中就有具体的错误原因说明了。
image.png

小结

通过Reporter对象,我们自己扩展了测试报告的内容,让结果更加容易定位。QTP虽然提供了不错的报告体系,但是并不能很方便的和别的系统对接,如果希望测试结果出错后自动提交缺陷或者发送电子邮件通知,就比较困难了。
另一方面QTP自己的报告体系只能使用它自己的工具才能打开,在阅读上并不是很方便,所以在后期框架设计上还是要丢弃臃肿的QTP Reporter对象,转入自定义体系架构中。

数据驱动模式

所谓的数据驱动不过是在执行逻辑相同的情况下,使用不同的数据从而实现同一流程的不同输入及断言。
在前面关于VBS代码介绍中就使用了这样的数据驱动模式,在这个章节中会继续加深这个概念。

DataTable

DataTable是QTP提供的一个数据源体系,使用起来还算是比较方便的。在使用DataTable之前首先建议大家关闭掉自动遍历功能。
image.png


在File菜单下的Test Settings中的Run标签中有Data Table iterations设置选项,这里需要把默认的Run on all rows修改为Run one iteration only,这样后面会用起来方便很多!

DataTable赋值及取值

在使用DataTable的时候,这里有个隐含的“坑”,首先找到DataTable,双击修改属性名。
image.png


DataTable中的A是一个假的属性,双击弹出的Change Parameter Name窗口中重新设置一个属性名,例如GD1。这样可以避免访问属性的时候出现一些找不到属性的错误。
编写代码为Global这个Sheet下的GD1属性赋值再取出输出,代码如下

  1. DataTable.Value("GD1","Global")="abcd"
  2. print DataTable.Value("GD1","Global")

DataTable.Value的第一个属性是要访问的表属性名,后一个属性是所在的Sheet名。为了更好的看到执行效果,在print代码前设置一个断点,Insert/Remove Breakpoint。
image.png


接着运行代码,就可以看到DataTable中的对应属性被赋值了。
image.png


点击Run继续运行完整个代码,可以看到日志中也出现了对应的内容。

DataTable遍历

由于前面一开始就关闭了默认的自动遍历,所以手动遍历成为了必备内容。
首先先来调整记录指针,这里需要用DataTable.SetCurrentRow这个方法。同样在print “wait”上面设置一个断点,运行就可以看到
image.png


可以看到记录一次被顺序存放在DataTable下的GD1属性中。
如果需要遍历DataTable那么就首先需要知道DataTable中的记录条数,这个要使用DataTable.getrowcount()函数。

  1. For i=1 to DataTable.GetRowCount
  2. DataTable.SetCurrentRow(i)
  3. print DataTable.Value("GD2","Global")
  4. Next

在DataTable中设置一个属性叫做GD2,然后运行上面的代码,就可以看到记录被遍历了。
image.png

DataTable导入导出

DataTable是一个很类似与Excel的表格,而且其直接兼容Excel2003格式。由于对DataTable的写内容是动态存储的,所以脚本运行完后就会消失,所以需要将其保存。
关于DataTable还有好些方法,但是有用的不多,而且DataTable并不是非常好用的方式,所以这里点到为止。后面会详细介绍下用VBS扩展的数据驱动模式。

File文件数据驱动

VBS提供了FSO对象体系,可以实现对文件内容的读写功能。

  1. Option Explicit
  2. Const ForReading = 1
  3. Const ForWriting = 2
  4. Const ForAppending = 8
  5. Dim fso,file,msg
  6. Set fso = CreateObject ("Scripting.FileSystemObject")
  7. Set file = fso.OpenTextFile (“c:\testdata.txt", ForReading)
  8. Do While Not file.AtEndOfStream
  9. msg = file.ReadLine
  10. print msg
  11. Loop
  12. file.Close
  13. Set file =Nothing
  14. Set fso = Nothing

OpenTextFile方法中的两个参数一个是文件名,另外一个是打开文件的方式,下面是写文件的例子。

  1. Const ForReading = 1, ForWriting = 2
  2. Dim fso, fp
  3. Set fso = CreateObject ("Scripting.FileSystemObject")
  4. Set fp = fso.OpenTextFile ( "c:\testfile.txt", ForWriting, True)
  5. fp.WriteLine "Hello world!"
  6. fp.WriteLine "cloud"
  7. fp.Close
  8. set fso=nothing
  9. set fp=nothing


Excel对象数据驱动

VBS中可以直接使用Excel对象模型,而且对于Excel中的元素操作可以通过Excel宏实现,而宏本身也是基于VBS语法的。

  1. Dim xlApp, xlFile, xlSheet
  2. Dim iRowCount, iLoop, numAdd
  3. Set xlApp = CreateObject ("Excel.Application")
  4. Set xlFile = xlApp.Workbooks.Open ("c:\data.xls")
  5. Set xlSheet = xlFile.Sheets("Sheet1")
  6. iRowCount = xlSheet.usedRange.Rows.Count
  7. For iLoop = 2 To iRowCount
  8. numAdd = xlSheet.Cells(iLoop,1)
  9. MsgBox numAdd
  10. Next
  11. xlFile.Close
  12. xlApp.Quit
  13. Set xlSheet = Nothing
  14. Set xlFile = Nothing
  15. Set lApp = Nothing

如果要写Excel可以这样写

  1. Set ExcelApp = CreateObject("Excel.Application") '创建EXCEL对象
  2. Set ExcelBook = ExcelApp.Workbooks.Add
  3. Set ExcelSheet = ExcelBook.Worksheets(1) '添加工作页
  4. ExcelSheet.Activate
  5. ExcelApp.DisplayAlerts = False
  6. ExcelSheet.Name="sheet1"
  7. ExcelSheet.Range("A1").Value = 100 '设置A1的值为100
  8. ExcelBook.SaveAs "c:\test.xls"
  9. '保存工作表
  10. print "d:\test.xls创建成功!"
  11. ExcelBook.close
  12. set excelApp=nothing
  13. set ExcelBook=nothing
  14. set ExcelSheet=nothing

如果要想要为Excel做点效果,比如颜色标记,都可以实现。下面这个例子会从表中读取一个输入测试用例等级,然后读取所有等级合适的用例依次计算并且将计算结果与列中的期望值比较,相等输出PASS并且标记RGB(20,255,50)否则输出Fail并且标记颜射RGB(255,20,50)

  1. level=InputBox("testcase level")
  2. Dim xlApp, xlFile, xlSheet
  3. Dim iRowCount, iLoop, numAdd
  4. Set xlApp = CreateObject ("Excel.Application")
  5. Set lFile = xlApp.Workbooks.Open ("c:\data.xlsx")
  6. Set lSheet = xlFile.Sheets("Sheet1")
  7. iRowCount = xlSheet.usedRange.Rows.Count
  8. For Loop = 2 To iRowCount
  9. numAdd1 = xlSheet.Cells(iLoop,1)
  10. numAdd2 = xlSheet.Cells(iLoop,2)
  11. numAdd3 = xlSheet.Cells(iLoop,3)
  12. numAdd4 = xlSheet.Cells(iLoop,4)
  13. numAdd5 = xlSheet.Cells(iLoop,5)
  14. If Int(numAdd3)<=CInt(level) Then
  15. result=Eval(Left(numAdd4,Len(numAdd4)-1))
  16. If Str(result)=CStr(numadd5) Then
  17. xlSheet.Cells(iLoop,6)="PASS"
  18. xlSheet.Cells(iLoop,6).Font.Color=RGB(20,255,50)
  19. Else
  20. xlSheet.Cells(iLoop,6)="Fail"
  21. xlSheet.Cells(iLoop,6).Font.Color=RGB(255,20,50)
  22. End if
  23. Else
  24. xlSheet.Cells(iLoop,6)=""
  25. End if
  26. Next
  27. xlFile.Save
  28. xlFile.Close
  29. xlApp.Quit
  30. Set xlSheet = Nothing
  31. Set xlFile = Nothing
  32. Set xlApp = Nothing

ADO对象数据驱动

通过ADO模型可以和各种数据库包括ODBC连接。

Access

通过ADO进行Access文件访问。

  1. Dim Cnn, Rst, strCnn
  2. Set Cnn = CreateObject( "ADODB.Connection“ )
  3. Set Rst = CreateObject( "ADODB.Recordset )
  4. strCnn= "Provider=Microsoft.Jet.OLEDB.4.0.1;Data Source=C:\cal.mdb; Persist Security Info=False"
  5. Cnn.Open strCnn
  6. Rst.Open "Select * from student", Cnn
  7. Rst.MoveFirst
  8. Do While Not Rst.EOF
  9. MsgBox Trim(Rst.Fields("name"))
  10. Rst.MoveNext
  11. Loop
  12. Rst.Close
  13. Cnn.Close
  14. Set Rst = Nothing
  15. Set Cnn = Nothing

SqlServer

通过ADO进行SqlServer连接。

  1. Dim Cnn,Rst,strCnn
  2. Set Cnn = CreateObject ( "ADODB.Connection“ )
  3. Set Rst = CreateObject ( "ADODB.Recordset )
  4. strCnn = "Provider=SQLOLEDB; User ID=sa;Password=sa; Data Source=localhost;Initial Catalog=pubs"
  5. Cnn.Open strCnn
  6. Rst.Open "Select * from authors",Cnn
  7. Rst.MoveFirst
  8. Do While Not Rst.EOF
  9. print Trim(Rst.Fields("city"))
  10. Rst.MoveNext
  11. Loop
  12. Rst.Close
  13. Cnn.Close
  14. Set Rst = Nothing
  15. Set Cnn = Nothing

如果进一步需要插入或者执行SQL命令需要这样写

  1. Dim Cnn,Rst,strCnn,AdoC
  2. Set Cnn = CreateObject ("ADODB.Connection")
  3. Set Rst = CreateObject ("ADODB.Recordset")
  4. Set AdoC = CreateObject("ADODB.Command")
  5. strCnn ="Provider=SQLOLEDB; User ID=sa;Password=51testing; Data Source=localhost;Initial Catalog=csharunit"
  6. Cnn.Open strCnn
  7. Rst.Open "Select * from addunittable",Cnn
  8. Rst.MoveFirst
  9. Do While Not Rst.EOF
  10. 'MsgBox Trim(Rst.Fields("input1"))
  11. 'MsgBox Trim(Rst.Fields("input2"))
  12. Rst.MoveNext
  13. Loop
  14. result="test"
  15. demo="pass"
  16. sql="insert into vbslog('result','demo') values('"&result&"','"&demo&"')"
  17. MsgBox sql
  18. Set Execsql=Cnn.Execute(sql)
  19. Rst.Close
  20. Cnn.Close
  21. Set Rst = Nothing
  22. Set Cnn = Nothing
  23. Set Execsql = Nothing

XML解析

  1. Dim xmlDoc, xmlRoot, ChildItem, msg
  2. Set xmlDoc = CreateObject ("Microsoft.XMLDOM")
  3. xmlDoc.Load "C:\test.xml"
  4. If xmlDoc.parseError.errorCode <> 0 Then
  5. print "XML loaded failed. :" & xmlDoc.parseError.reason
  6. Exit Sub
  7. End If
  8. Set xmlRoot = xmlDoc.documentElement
  9. If xmlRoot.hasChildNodes Then
  10. For Each ChildItem In xmlRoot.childNodes
  11. If ChildItem.nodeName = "name" Then
  12. print ChildItem.firstchild.nodeValue
  13. End If
  14. Next
  15. else
  16. print "error"
  17. end if

小结

数据驱动不过就是一个读取数据反复调用对应的对象或者输入,一切的基础是在于你功能测试用例的高内聚,并且需要有大量输入来实现各种逻辑覆盖。
但是换个角度来说,其实接口自动化远比UI自动化的数据驱动效率高很多,而且效果还一致,这也是我一直不太看好UI自动化的原因之一。

对象同步及Wait模式

在脚本执行的时候经常会出现各种对象找不到,而检查一下都是正确的定位,主要原因还是因为脚本执行太快了,而被操作对象还没出现,所以对象同步等待就成为了必须的东西。
Wait是一种最简答的方式,也就是直接物理等待,基本上很好用但是也很不好用,因为物理等待会降低代码的执行效率,而对象同步等待会更加合适。
image.png


默认情况下QTP提供了20秒的全局对象同步等待超时,也就是说如果一个对象没有识别到,那么它会一直尝试等待20秒,如果20秒后还是没有识别到该对象,就提示该对象识别错误的信息了。

WaitProperty

在某些情况下,我们需要等待一个按钮比较长的时间,例如倒计时,那么这个时候要么用wait做硬等待,要么就用WaitProperty来做对象属性等待。
WaitProperty在一些开源工具中也有封装,本质就是循环等待一个对象的属性,如果匹配了就跳出,否则就等待超时错误。
在QTP中的任何一个对象都提供了waitproperty的方法,例如等待某个按钮可以这样写:

  1. Browser("br").Page("UI Automation Testing").WebButton("Submit").WaitProperty "disabled","0",10

由于这个按钮默认是被禁用的,所以为了能够点上这个按钮,应该等待该对象的disabled的值为0。
image.png

全局等待与WaitProperty

首先把全局等待时间从20秒改成5秒,然后再次运行这个脚本。
image.png


再把全局时间从5改回20,运行一次,就会发现前面一次的运行速度会比后面一次快很多,在脚本中添加一个事务。

  1. Services.StartTransaction "aa"
  2. Browser("br").Page("UI Automation Testing").WebButton("Submit").WaitProperty "disabled","0",10
  3. Services.EndTransaction "aa"

通过测试报告中可以看到,全局时间决定了所有的对象等待时间,只要全局超时,WaitProperty也不会等待了。
image.png

SYNC

对于网页中页面加载比较慢的情况可以通过SYNC方法等待加载完毕。

  1. Browser("br").Page("UI Automation Testing").Sync

小结

在全局等待的大前提下,可以通过Wait来物理等待时间,但更多的时候为了提高代码执行的效率,使用WaitProperty更加合适,这样可以避免无谓的时间浪费。

描述性编程

所谓的描述性编程只不过就是用代码直接来说明被访问对象而已,对比对象库体系,这种方式使用起来比较直接,但是并不是说明它就好。

描述性编程1

第一种要介绍的描述性编程是直述型的,简单来说就是直接在代码里面说明要访问的对象属性。

  1. Window("regexpwndtitle:=计算器").highlight
  2. Window("regexpwndtitle:=计算器").WinButton("window id:=132").highlight
  3. Window("regexpwndtitle:=计算器").WinButton("window id:=132").click

属性和属性名之间使用:=隔离,只要属性匹配即可,上面的代码使用了一个隐藏的方法hightlight,高亮被访问到的对象。
如果需要匹配多个对象,那么使用逗号分离。

  1. For i= 131 to 139
  2. Window("regexpwndtitle:=计算器","regexpwndclass:=CalcFrame").WinButton("window id:="&cstr(i)).click
  3. Next

由于使用了描述性编程,这样在变更对象识别的时候会非常的方便,但是缺点是每一个对象都要重复的进行描述,对于某些通用的对象有些太过繁琐了,所以对象型描述性编程成为了主流。

描述性编程2

VBS同样支持面向对象的模式,这样就可以使用对象来存放对象属性了。

  1. set calcwindow = Description.Create()
  2. calcwindow("regexpwndtitle").Value = "计算器"
  3. calcwindow("regexpwndclass").Value = "CalcFrame"
  4. set calcbutton=Description.Create()
  5. calcbutton("window id").Value="131"
  6. Window(calcwindow).WinButton(calcbutton).Click

通过set来设置对象,然后就可以对这个对象进行赋值,进一步存放对象传递对象,所以这种描述性编程在实际使用中更加常见。

DP vs OP

DP描述性编程和OP对象库编程两种模式在业内有很多争论,对于初学者来说从OP开始入门一旦了解了DP,就会被DP的自由度和可编程性吸引,然后觉得掌握了DP就掌握了技术。其实无论是DP还是OP都只是技术实现的一种手段,而OP从使用便捷程度和移植性上其实都比DP要好很多,而且QTP还针对对象库提供了大量自动化模块的支撑,这样才使得录制下来的对象回放成功率非常的高。
从懂到懂再到真的懂就好像从OP到DP再回到OP一样,最终你会发现OP已经帮你实现了你想在DP里面做的一切。

小结

描述性编程是一种脱离QTP体系,走向编程体系的关键步骤,也是QTP中相对来说比较“难”的技术点,其难不过是需要对被访问对象的识别原理彻底掌握。

子对象遍历

QTP实现所有的一切都来自于遍历,而遍历的基础在于子对象。对于所有的对象都有一个ChildObject子对象方法。

ChildObjects

ChildObjects给我们提供了一种方法来遍历子对象,比如我们想知道winXP计算器标准型下有多少个子对象,只需要这样写就行了。

  1. set mychild=Window("regexpwndtitle:=计算器").ChildObjects()
  2. print mychild.count

接着把计算器切换一下到科学型,再运行一次,可以看到标准型计算器有31个子对象,而科学型计算器有75个对象。
image.png


接着来看看这些子对象到底是什么,访问子对象需要用到RunObject。

  1. set mychild=Window("regexpwndtitle:=计算器").ChildObjects()
  2. print mychild.count
  3. For i=0 to mychild.count-1
  4. print mychild.item(i).GetROProperty("text")
  5. print mychild.item(i).GetROProperty("nativeclass")
  6. Next

可以看到代码运行完成后,每个对象的Text属性被输出了。
image.png

描述性编程与正则表达

描述性编程支持正则表达式,可以通过通配的模式来识别对象。比如我只想点一个文字中含有确定的按钮或者7位长度的电话号码对象点击,这些时候正则表达式就可以发挥作用了。

  1. set calcwindow = Description.Create()
  2. calcwindow("regexpwndtitle").Value = "计算器"
  3. calcwindow("regexpwndclass").Value = "CalcFrame"
  4. set calcbutton=Description.Create()
  5. calcbutton("window id").Value="131"
  6. calcbutton("window id").RegularExpression = false
  7. Window(calcwindow).WinButton(calcbutton).Click

默认描述性编程的正则表达式是启动状态的。
为了避免正则表达式的启动,可以通过RegularExpression为false。正则表达式中匹配中会涉及到多项匹配,通过额外的属性或者ChildObjects过滤来识别。

ChildObjects过滤

ChildObjects可以根据对象进行过滤,配合描述性编程的正则表达式可以实现对某一类对象的动态获取及操作。

  1. SystemUtil.CloseProcessByName "calc.exe"
  2. SystemUtil.Run "calc.exe"
  3. set mycalc= Description.Create()
  4. mycalc("regexpwndtitle").Value = "计算器"
  5. set mybutton= Description.Create()
  6. mybutton("nativeclass").Value = "button"
  7. mybutton("text").Value = "\d"
  8. window(mycalc).Activate
  9. set x=window(mycalc).ChildObjects(mybutton)
  10. If x.count=1 Then
  11. print "Object Unique"
  12. window(mycalc).winbutton(mybutton).Click
  13. else
  14. print "Object not unique"+cstr(x.count)
  15. End If
  16. For i=0 to x.count-1
  17. print x.item(i).GetROProperty("text")
  18. print x.item(i).GetROProperty("nativeclass")
  19. Next

恢复场景

恢复场景顾名思义就是在某些情况下恢复到一个状态,在自动化测试过程中经常会遇到被测应用意外Crash导致自动化无法继续执行,或者某一个步骤无法继续导致自动化无法继续执行。这个时候就需要恢复场景来处理这种情况,确保自动化能够继续运行,比如重启应用,回退步骤,甚至初始化数据。
QTP恢复场景是非常值得参考的,在所有的自动化框架中,QTP的恢复场景做的都是很优秀的。
image.png


在Resources内提供了Recovery Scenario Manager恢复场景,点击启动。
image.png


这里选择新建一个恢复场景。
image.png


恢复场景有一个向导,通过设置触发规则和恢复方式来完成恢复场景。
image.png


这里提供了4种触发方式,
image.png

  1. 弹出窗口
  2. 对象状态
  3. 测试运行错误
  4. 应用Crash

在这四种情况下,都会触发后面的恢复事件。

Select Trigger Event

Pop-up Window弹出窗口

当某个窗口弹出时,根据弹出窗口的标题或者是正文来判断是否触发。
image.png


标题和正文都支持正则表达式,只要通过手指选择合适的对象即可。这里选择Win7版计算器(程序员),可以看到识别到的内容为。
image.png

Object State对象状态

对象状态就是对象的属性,通过选择对象并验证属性即可。
image.png


在计算器上做一个除零的错误情况然后在恢复场景中选择计算结果框
image.png


接着就可以为对象属性设置触发规则,添加属性text,其属性为“除数不能为零”
image.png

Test Run Error

这个就比较好理解了,也就是如果测试的结果错误,就进行触发。
image.png

Application Crash

应用挂起的触发就是以进程名来作为判断的。
image.png


将需要判断的进程名添加到右侧即可。

Recovery Operations

当有多个恢复策略的时候在列表中就会出现,而且可以调整先后顺序。
image.png


Add another recovery opteration该选项会退出Recovery而进入Post-recovery选项。
QTP恢复场景提供了4种方式。
image.png


其中Function call提供了对于函数的调用。
image.png


*这里有一个问题,写的标准格式的函数库不能被正确加载可能是版本问题。

Post-recovery

在配置了恢复触发的操作后,最后可以配置的就是恢复操作后的步骤。
image.png


设置重新运行当前步骤还是重新运行整个测试用例等,最后配置名称保存为qrs文件即可。

恢复场景的执行效果

当设置了恢复场景,在最终的报告中会出现触发的记录,不过需要先在脚本设置中加载前面保存的恢复场景。
image.png


一般来说推荐设置触发机制为On every step,就是每步都验证,这样可以捕获所有的异常。
执行脚本后,当出现对应的状态捕获就触发了恢复场景
image.png


默认的测试报告里面没有截图,可以通过配置把截图甚至是视频和当时的系统资源都添加进报告。
image.png


在Options里面可以配置截屏的方式及保存录屏的模式,而在脚本设置中可以配置监控的资源计数器。
image.png


再次运行后错误的内容就更丰富了。
image.png


*这里的截图不会自动补充到恢复步骤上,而视频开启后会出现QTP异常退出的问题,可能是版本导致。

小结

恢复场景是一个非常好的自动容错机制,它帮助我们智能的继续着自动化脚本,来跳过一个个错误和异常。
我们常见的恢复内容可以包括:

  1. 对象没识别到再试一次?
  2. 对象识别不唯一,自动试试以前的某个属性或者另外一套属性?
  3. 系统崩溃了自动重启应用?
  4. 出现了没有权限提示的情况自动调用一个函数给一个临时权限?
  5. 如果出错了记录下当时的详细系统信息?

可以做的事情很多,通过这套通用的处理机制可以保护着自动化脚本在各种情况下达到其执行的目的。

AOM自动化对象模型

AOM也是 Automation Object Model的缩写,而QTP也具备自己一套AOM体系。简单来说AOM就是工具本身完全支持代码的自动化执行,而不需要使用任何人工操作来干预,每一个选项每一个功能都能支持自动化(QTP并不能完全支持,例如虚拟对象和恢复场景,还是需要事先配置好调用)。
学习QuickTest Automation Object Model其实只需要看一个文档就行了,就是QTP自带的 QuickTest Automation Reference。
image.png


在这个文档中描写了如何对QTP进行完全自动化的代码和对象操作,回忆参数化中的关于Excel对象模型、FSO对象模型、XML对象模型等其实也是AOM的一种。

  1. '************************************************************************************************************************
  2. 'Description:
  3. '
  4. 'This example opens a test, configures run options and settings,
  5. 'runs the test, and then checks the results of the test run.
  6. '
  7. 'Assumptions:
  8. 'There is no unsaved test currently open in QuickTest.
  9. 'For more information, see the example for the Test.SaveAs method.
  10. 'When QuickTest opens, it loads the add-ins required for the test.
  11. 'For more information, see the example for the Test.GetAssociatedAddins method.
  12. '************************************************************************************************************************
  13. Dim qtApp 'As QuickTest.Application ' Declare the Application object variable
  14. Dim qtTest 'As QuickTest.Test ' Declare a Test object variable
  15. Dim qtResultsOpt 'As QuickTest.RunResultsOptions ' Declare a Run Results Options object variable
  16. Set qtApp = CreateObject("QuickTest.Application") ' Create the Application object
  17. qtApp.Launch ' Start QuickTest
  18. qtApp.Visible = True
  19. ' Make the QuickTest application visible
  20. ' Set QuickTest run options
  21. qtApp.Options.Run.ImageCaptureForTestResults = "OnError"
  22. qtApp.Options.Run.RunMode = "Fast"
  23. qtApp.Options.Run.ViewResults = False
  24. qtApp.Open "C:\Tests\Test1", True
  25. ' Open the test in read-only mode
  26. ' set run settings for the test
  27. Set qtTest = qtApp.Test
  28. qtTest.Settings.Run.IterationMode = "rngIterations"' Run only iterations 2 to 4
  29. qtTest.Settings.Run.StartIteration = 2
  30. qtTest.Settings.Run.EndIteration = 4
  31. qtTest.Settings.Run.OnError = "NextStep"' Instruct QuickTest to perform next step when error occurs
  32. Set qtResultsOpt = CreateObject("QuickTest.RunResultsOptions") ' Create the Run Results Options object
  33. qtResultsOpt.ResultsLocation = "C:\Tests\Test1\Res1"' Set the results location
  34. qtTest.Run qtResultsOpt ' Run the test
  35. MsgBox qtTest.LastRunResults.Status ' Check the results of the test run
  36. qtTest.Close ' Close the test
  37. Set qtResultsOpt = Nothing ' Release the Run Results Options object
  38. Set qtTest = Nothing ' Release the Test object
  39. Set qtApp = Nothing' Release the Application object

这是一个最基本的QTP AOM代码结构,其中包括了QTP默认设置的一些设置方式,脚本运行及结果保存地址。通过这个VBS文件运行后就可以完成QTP的自动调用,那么事先写好的脚本就会被执行。

框架设计思路简介

在拥有了AOM后,就可以来谈一下框架了,所谓的框架就是超出QTP,重新做一套类似于QTP的东西。
何谓超出QTP,就是要摆脱它的限制帮助我们方便的和各种系统对接,而类似呢就是需要具备关键字驱动、数据驱动、报告库、对象库、恢复场景等功能,从某个角度来说其实我们真的做不到QTP那么好,但是做的大而全反而不适用于自己的情况,所以不求做的多大而全,好用就行了。

加载及扩展

在谈框架别的东西之前首先要谈个最基本的东西如何加载相关模块,QTP是从脚本开始的,也就是Main主函数的概念,由于VBS的加载体系的缺陷(内存和对象管理),使用VBS做QTP的AOM是一件非常恶心的事情,虽然开始做起来蛮简单的,越往后越麻烦,而且会越用越慢。

整体加载

在代码中如果我想加载一个function函数或者sub过程(VBS概念),就可以使用executefile这个函数来实现。
这是我的一个main.vbs里面的代码,而在整个QTP项目中只有一句话,就是
当AOM的启动后,会去加载main.vbs然后main.vbs会去加载对应的后续模块。

局部加载

使用整体加载有个缺点,可能一个模块很少被使用,但是一旦被加载就意味着一直在内存中了。为了减少对系统资源的占用,对于某些突发的模块,可以使用动态加载的模式来实现,动态加载的函数是executeglobal。
这个函数可以帮助我们动态的拼接命令并且执行,这样就可以处理某些动态的库体系了。

模块划分

在有了加载模块的代码后,就可以划分模块实现高内聚低耦合了。

命令模块

在命令模块中主要就是存放一些常用的命令体系,比如发送邮件,加载编译模块等。
这里通过wscript.shell的扩展完成了对命令行的调用,可以执行各种bat文件。

对象库模块

在对象库模块中可以使用描述性编程模式,也可以直接使用QTP的对象库。

启动模块

启动和关闭被测对象进程或者相关应用的模块。

数据驱动模块

在数据驱动部分就需要考虑用什么格式来传递数据很重要,自定义数据结构。这里可以参考前面的数据驱动。

操作模块

用来封装对对象的一些操作模式。

被测对象独立模块

每一个被测对象都自己特殊一些的业务逻辑也就是测试用例,所以会对该被测对象进行用例的管理体系。

其它模块

除了上面的模块,还可以根据自己的需求设置:
1.
配置模块(一些参数和常量的配置)
2.
报告模块(报告体系,截图、日志)
3.
接口模块(与扩展别的体系对接)
4.
恢复模块(当出错的时候如何恢复)
5.
分布式执行模块(多个平台分布的配置)

小结

对于一个框架最重要的就是高内聚低耦合,如何根据自己被测对象的特点来定制轻量化的框架体系才是关键,所以不要太在意别人的框架有多么豪华,关键是对自己有用的是多少。
而在这里大家也能感觉到VBS语法所带来的问题,它无法像Java或者Python这类的语言有非常强大的开发及跟踪调试功能,扩展的库相对少而且使用复杂,稳定性和性能都存在较大的缺陷,这也是为什么QTP逐渐走向没落的原因之一,幸好还有类似Ranorex、SilkTest这类的工具存在,避免了无(商业)工具可用的情况。
自己的框架是跟随着系统的发展逐步发展的,这样才能做出好用的框架。
*注:
由于早在2009年左右写过的几个框架都找不到了,所以这里罗列出来的框架代码非常的松散和不成形,当年在上周末班的时候写过几个稍微规模大一点的框架,在数据的传递和结构的拆分上做的更精巧点。

后记

这本书早可以在5年前写出来,相对LR来说QTP能折腾的东西少一些,可以这样说整个自动化就是在编程,写一个系统去运行另外一个系统而已。
对于小白来说面对圈子中那么多的技术名词和花样繁多的工具,总是在技术的细节上苦苦挣扎而缺乏跳出障目一叶的机会。在编写本书的过程中,我跳过了大量的技术分支细节,就像开篇写的一样,学习QTP的目的是为了深入的理解对象识别的原理,进一步掌握QTP在框架设计上的种种设计及规划,从而为学习别的自动化体系提供基础和思路,最终要解决的问题其实只有两个字“编程”。
而对于阅读完本文的你来说,是不是能够把复杂的自动化看简单了呢?这是作为作者我最想听到的话,而在后续的自动化学习中,当你不断的告诉自己自动化就这个样子“没啥技术含量”的时候,希望你再来和我唱Only you。
云层
2016-02-15