精通QTP

2015q 贡献于2015-03-15

作者 cross  创建于2015-03-14 06:59:00   修改者ihonG _  修改于2015-03-14 06:59:00字数170531

文档摘要:章节的内容主要讲的是QTP的录制与回放,市面上,任何一本书都会有这部分的内容。但是,作者对这部分内容进行了提炼,把其他书里的那些几乎等于QTP工具介绍的内容全部剔除,因为那些内容在实际的项目应用中几乎不会用到,保留了能切实在项目中用到的内容,通过自己的实战经验配合大量案例、实例(几乎每一个知识点都有例子)进行细致地讲解,希望读者在学习了本章后,能对QTP这个自动化测试工具的操作,以及如何把工具融合实际项目上有个整体的把握。虽然剔除了很多不需要的内容,但是,由于介绍的是QTP的源头(就好比本章节标题一样“QTP的开关”)而且配合了大量的实例,图文并茂,所以在完成本章节的编写后才发现原来还是需要用到那么大的篇幅才能将重要的知识点讲清楚、讲透。
关键词:

1.3.5 总结   本章节的内容主要讲的是QTP的录制与回放,市面上,任何一本书都会有这部分的内容。但是,作者对这部分内容进行了提炼,把其他书里的那些几乎等于QTP工具介绍的内容全部剔除,因为那些内容在实际的项目应用中几乎不会用到,保留了能切实在项目中用到的内容,通过自己的实战经验配合大量案例、实例(几乎每一个知识点都有例子)进行细致地讲解,希望读者在学习了本章后,能对QTP这个自动化测试工具的操作,以及如何把工具融合实际项目上有个整体的把握。虽然剔除了很多不需要的内容,但是,由于介绍的是QTP的源头(就好比本章节标题一样“QTP的开关”)而且配合了大量的实例,图文并茂,所以在完成本章节的编写后才发现原来还是需要用到那么大的篇幅才能将重要的知识点讲清楚、讲透。   在设计练习题的方面,作者思虑许久,考虑到本章节内容的性质,决定在本章节的巩固练习题中不设置实际操作题,本章节的练习题如果要出成操作题,无非就是传统的“录制与回放”,这样就会不由自主地偏离作者的原有思想。所以,作者决定以选择题(单选和多选)、判断题、排列题等各类有趣的形式出一份类似Exam一样的概念理论题,这些所谓的理论题其实是对整个章节内容知识点的贯穿,希望能起到巩固与复习的作用。   知识点巩固和举一反三练习   一、知识加强巩固趣味题之过5关(注:选择题可能是单选也可能是多选)   1、<排列题> 请排列QTP业务操作及执行流程的顺序。 ()   A、录制  B、打开浏览器  C、回放  D、打开QTP   2、<选择题> QTP的两种脚本运行模式分别是Normal和Fast,它们之间的区别是什么?请在下面4个选项中做出正确的选择。 (  )   A、以QC调用的方式执行脚本,运行模式是Fast。   B、脚本运行时,如果左侧有黄色箭头,则代表这种运行模式是Fast。   C、Normal运行模式的执行速度最快。   D、可以在Normal运行模式中设置间隔时间,单位是毫秒。   3、<选择题>请在以下4个选项中选择正确的选项。 (  )   A、QTP停止运行的快捷键是Ctrl+Alt+F5,但不提供个性化设置。   B、QTP具有Event和Mouse两种回放模式。   C、QTP具有Slow、Normal和Fast 3种运行模式。   D、QTP具有两种录制模式,它们是Analog Recording和Low Level Recording。   4、<选择题> 请在以下4个选项中选择错误的选项。 (  )   A、假设在运行设置中选择Run on all rows选项,在QTP的Data Table中建立4行数据,其中第2行的值为空,则QTP在执行两次迭代后运行结束。   B、如果激活Run one iteration only选项,QTP在运行时只运行1次   C、Browser navigation timeout:XX seconds就是设置运行测试步骤之前,等待网页加载时间的最大值,超出了该值后脚本直接退出并报错。   D、QTP的运行模式设置具体位置在:Tools→Options→Web→Advanced→Run settings下的Replay type。 1.3.5 总结   本章节的内容主要讲的是QTP的录制与回放,市面上,任何一本书都会有这部分的内容。但是,作者对这部分内容进行了提炼,把其他书里的那些几乎等于QTP工具介绍的内容全部剔除,因为那些内容在实际的项目应用中几乎不会用到,保留了能切实在项目中用到的内容,通过自己的实战经验配合大量案例、实例(几乎每一个知识点都有例子)进行细致地讲解,希望读者在学习了本章后,能对QTP这个自动化测试工具的操作,以及如何把工具融合实际项目上有个整体的把握。虽然剔除了很多不需要的内容,但是,由于介绍的是QTP的源头(就好比本章节标题一样“QTP的开关”)而且配合了大量的实例,图文并茂,所以在完成本章节的编写后才发现原来还是需要用到那么大的篇幅才能将重要的知识点讲清楚、讲透。   在设计练习题的方面,作者思虑许久,考虑到本章节内容的性质,决定在本章节的巩固练习题中不设置实际操作题,本章节的练习题如果要出成操作题,无非就是传统的“录制与回放”,这样就会不由自主地偏离作者的原有思想。所以,作者决定以选择题(单选和多选)、判断题、排列题等各类有趣的形式出一份类似Exam一样的概念理论题,这些所谓的理论题其实是对整个章节内容知识点的贯穿,希望能起到巩固与复习的作用。   知识点巩固和举一反三练习   一、知识加强巩固趣味题之过5关(注:选择题可能是单选也可能是多选)   1、<排列题> 请排列QTP业务操作及执行流程的顺序。 (  )   A、录制  B、打开浏览器  C、回放  D、打开QTP   2、<选择题> QTP的两种脚本运行模式分别是Normal和Fast,它们之间的区别是什么?请在下面4个选项中做出正确的选择。 (  )   A、以QC调用的方式执行脚本,运行模式是Fast。   B、脚本运行时,如果左侧有黄色箭头,则代表这种运行模式是Fast。   C、Normal运行模式的执行速度最快。   D、可以在Normal运行模式中设置间隔时间,单位是毫秒。   3、<选择题>请在以下4个选项中选择正确的选项。 (  )   A、QTP停止运行的快捷键是Ctrl+Alt+F5,但不提供个性化设置。   B、QTP具有Event和Mouse两种回放模式。   C、QTP具有Slow、Normal和Fast 3种运行模式。   D、QTP具有两种录制模式,它们是Analog Recording和Low Level Recording。   4、<选择题> 请在以下4个选项中选择错误的选项。 (  )   A、假设在运行设置中选择Run on all rows选项,在QTP的Data Table中建立4行数据,其中第2行的值为空,则QTP在执行两次迭代后运行结束。   B、如果激活Run one iteration only选项,QTP在运行时只运行1次   C、Browser navigation timeout:XX seconds就是设置运行测试步骤之前,等待网页加载时间的最大值,超出了该值后脚本直接退出并报错。   D、QTP的运行模式设置具体位置在:Tools→Options→Web→Advanced→Run settings下的Replay type。 1.4 认清并请远离QTP的脚本录制模式   阶段要点   ● 了解Keyword View结构。   ● 摆脱Keyword View。   ● 熟练掌握Expert View的代码结构。 1.4.1 QTP的两种视图及思维转换   从本章节开始,读者将真正开始彻底摆脱脚本录制,正式走向脚本开发的台阶。在开始讲解QTP脚本开发之前,首先需要详加介绍Expert View这个视图,这个视图是QTP官方指定的脚本开发唯一认可的视图。那么,以何为出发点以及如何介绍Expert View这个视图呢?决定以抛砖引玉的方式,利用脚本录制视图Keyword View形象地引出脚本开发视图Expert View。   相信只要稍许接触过QTP的读者就一定知道QTP提供两种视图,一种是Keyword View(关键字视图),另一种是Expert View(专家视图)。Mercury公司开发两种视图的本意是想让不同类型的人使用不同类型的视图。接下来分别对这两个视图进行讲解,并开始抛砖,直到引出一块良田美玉。   1.Keyword View(关键字视图)   通过关键字视图(一种图形化的视图),QTP提供了一种模块化的表格格式,创建和查看测试或组件的步骤。在录制脚本的过程中,用户在应用程序上执行的每个步骤,在关键字视图中记录为一行,这样用户就可以轻松地修改任何一部分业务步骤。在这里拿“百度”的首页举一个例子,业务操作分3个步骤:   点击“图片”链接;   在图片搜索栏中输入“凤姐”;   点击“百度一下”。   在完成了这3个步骤后,关键字视图将包含下列行,如图1-47所示。 图1-47   图1-47中可以看到关键字视图非常直观有效,使用的用户可以很清晰地看到被录制对象的录制层次及运行步骤。但是,如果想自动化完成一些复杂的业务操作,在这张视图里是绝对不可能的,那就必须切换到专家视图里才行。专家视图等一切事宜就暂且搁置一下。先掌握关键字视图的界面、原理和工作机制。   在关键字视图中,一共分4列:Item、Operation、Value和Documentation。   Item:记录了所有对象。   Operation:该对象的操作。   Value:对象操作所用到的值。   Documentation:QTP自动生成的描述语句,描述了是什么对象,做了什么,怎么做。   关键字视图的介绍马上就要结束了,最后作者分解这个视图(语句会很随意)后会直接进入专家试图的解说。前面说过“在录制脚本的过程中,用户在应用程序上执行的每个步骤在关键字视图中记录为一行”,在本例中一共记录到6个步骤,如下:   ● 打开浏览器;   ● 进入百度首页;   ● 点击[图片]链接;   ● 进入图片页面;   ● 在[搜索框]输入“凤姐”;   ● 点击[百度一下]。 2.Expert View(专家视图)   瞬移成功,首先肯定是要讲专家视图的工作原理,“QTP在关键字视图中的每个节点在专家视图中对应一行脚本”。读者可能一下子不明白这句话,那先来看两段代码,第一段代码是将关键字视图的工作原理用在专家视图上的错误代码,转换后的脚本如下: Browser("百度一下,你就知道") '图1-47中页面1部分 Page("百度一下,你就知道") Link("图 片").Click '图1-47中页面2部分 Page("百度图片—全球最大中文图片库") WebEdit("word").Set "凤姐" WebButton("百度一下").Click   再看一下专家视图中正确的代码: '图1-47中页面1部分 Browser("百度一下,你就知道").Page("百度一下,你就知道").Link("图 片").Click '句1 '图1-47中页面2部分 Browser("百度一下,你就知道")._ Page("百度图片—全球最大中文图片库").WebEdit("word").Set "凤姐" '句2 Browser("百度一下,你就知道")._ Page("百度图片—全球最大中文图片库").WebButton("百度一下").Click '句3   看完这两段代码以后,再回过头来理解专家视图的工作原理,专家视图和关键字视图是完全不同的,用通俗点的讲,专家视图并不像关键字视图那样,每个步骤都在视图中记录成一行,在专家视图中,必须定位到业务操作最终的对象,并且每一句的结束,以及最终的对象的业务行完毕为基准。在这里,最终的对象就是节点,专家视图中的一句代码是关键字视图中好多个步骤的结合。而那段作者自己伪造的代码就不是,每一句都不完整,要么有头没尾,要么无头有尾,这就是Keyword View和Expert View最根本的区别,下面,一句句看专家视图中的代码构成,先以“句1”来说,“句1”中集合了3个对象。   Browser—该对象如果出现必定是第一层,可以把它想象成打开的一个指定浏览器,具体哪个浏览器那么就由后面括号里的参数决定,如:Browser("百度一下,你就知道"),这样QTP就能很准确地识别到底打开的是哪个浏览器了。   Page—浏览器下肯定有各种各样的网页。那要定位这个对象也就很简单了,和上面一样,在后面的括号内加参数,如:Page("百度一下,你就知道"),这样QTP就知道了,原来是要定位一个叫“百度一下,你就知道”的网页,然后其他网页会一概不理!   Link—在定位了前两个对象后,终于可以定位到要进行业务操作的对象了。可是,页面上如果有好多Link呢,怎么办?只能和之前一样,在后面的括号内写进参数,如Link("图 片"),这样QTP就不会乱了!好了,在最终找对了要操作的对象后,就可以给出指令,命令它做事情了,如需要它进行点击操作,那就给Link对象来个Click方法,到此,整个语句就可以完毕了。   分析:“句1”已经被拆解,作者在这里用最通俗的语言方式连贯地叙述一遍“句1”,希望能让新人听明白。→在一个叫做“百度一下,你就知道”的浏览器下的一个名曰“百度一下,你就知道”的网页页面上找到一个叫“图片”的链接,找到后,点击它。 小提示:   在上面的例子中,是以Browser().Page().Link()的结构出现的,但实际上,在Web测试中并不只有这一种固定形式,除了Browser必定是在第一层以外,Page以及Link(或其他所有对象控件,如WebEdit等)都不是固定必须要接在前面那层的后面的,比如:   (2)Browser().Page().Frame().Link():Link对象跟在Frame对象后面而没有跟在Page后面。   (2)Browser().Dialog().Page().WebElement():Page对象跟在Dialog对象后面的一个例子。   接下来再看看“句2”,这句代码中需要注意Page已经变化了,变成了Page?("百度图片—全球最大中文图片库"),然后在该页面中找到对象WebEdit("word"),最后给这个对象一个Set的操作方法并在后面写入参数,使得QTP在该页面上找到关键字搜索框,输入“凤姐”。   最后的“句3”唯一和前者的变化就是最终操作的对象不同了,Browser不变,页面仍然是Page("百度图片—全球最大中文图片库"),最后在前面输入好参数的前提下对WebButton?("百度一下")这个控件进行一个点击的操作,使得百度图片搜索引擎能够搜索到一些凤姐的照片 ************************************终极分析************************************   以上可以看到Browser对象一直使用的是同一个,随着Page对象的改变而分成了2个分支,在不同的分支上定位到不同的最终目标并赋予“动作”。所以,现在应该知道关键字视图中的代码结构了。就是通过这样的一层层定位,直到定位到最后的待操作对象,从而在Expert View下完美地组成一句脚本代码。   注:需要引出下一层对象用“.”,直到定位到最后一个待操作对象,然后仍然用“.”赋予其动作(各种不同的方法)。   小提示:在QTP中,要找到具体对象,如“图片”这个Link,规则就是在对象大类(Link类)后面以括号+引号的形式具体定位到那个Link控件,比如Link("图片")。需要注意的是,输入括号和引号乃至其他任何符号都必须是在英文状态下。切记!很容易出现类似的问题,在中文状态的输入法下打引号,结果QTP报错,自己找了好长时间也找不出问题。   概括&小结:   对于QTP 来说,绝大部分的复杂操作都无法在关键字视图中实现,例如,要处理动态对象、需要定制个性化测试结果、获取对象运行时的属性值(Run-time Value)等。   QTP的核心编码语言是Visual Basic Script,因此,如果读者熟悉VBScript,就可以运用自如地通过编程方式在专家视图中设计复杂的测试脚本。同样,QTP本身的对象库编程(Object Programming)和描述性编程(Descriptive Programming),这一切也都必须在专家视图中才能完成。   关于对象库编程、描述性编程、VBScript编程、Run-time Value、处理动态对象等一系列知识都会在后面的章节中逐步向读者呈现。 1.4.2 总结   不知道有些读者会不会有这样的疑问,本书既然一向有“彻底抛弃脚本录制”的理念,那为什么还要写Keyword View(关键字视图)的内容呢,就算要写只一句话带过不就得了!作者由衷地希望有这些疑问的读者越多越好,这说明广大读者就是冲着“抛弃录制”的思想购买这本书的。现在听作者解释一下吧。   作者这么做就是因为想抛出一块好砖引出更好的玉,在抛砖的时刻(笼统、重点配合一个实例介绍关键字视图)如果能做,越有声色,后面引出的玉一定就能更好(将关键字视图中的步骤“画面”,一条一条转进专家视图使其成为脚本代码的一个解读过程)。另外,请重新回顾下这个小章节标题的后半部分“思维转换”。作者觉得更应该引导读者转换的是思想、思维,绝对不该是两个视图间的转换那么简单与粗糙。读者要从思想上去认识Expert View,如果光从操作上直接转向Expert View(直接在关键字视图敲击代码),表面上看很简单很轻松,但其实忽略了对专家视图的认识,也忽略了对QTP两个视图原理以及之间关系和不同点上的认识,如果这样走下去,作者觉得在今后脚本开发的学习道路上挫折会越来越多,地基打好的关键性大家都很明白。相信,本书的这一个小章节内容虽少,但是对今后的帮助不一定少!   知识点巩固和举一反三练习   一、大家来找茬(结合图1-48找出代码片中的错误) 图1-48   代码片如下: Browser("百度一下,你就知道").Page("百度一下,你就知道").Link("知 道").Click Browser("百度百科—全球最大中文百科全书")._ Page("百度百科—全球最大中文百科全书").WebEdit("word").Click Browser(百度一下,你就知道)._  Page("百度百科—全球最大中文百科全书").WebButton("进入词条").Set "51testing"   二、请根据图1-49中关键字视图的结构与步骤转化成专家视图中的代码。 图1-49 1.5 QTP精华—对象库(上)之基础攻略篇   阶段要点   ● 初步了解QTP中的测试对象模型。   ● 明确Object Identification是管理对象模型的长官。   ● 掌握智能识别原理、机制和各项设置。   ● 对象库基本操作之添加、更新、对象闪烁、副对象库(Associate Repositorys)。   ● 一个有趣的实验证明,做项目时手工添加对象的好处与效率。   ● Export Local Objects与Export and Replace Local Objects。   ● 掌握并熟练运用Object Spy。   ● 明确使用公共对象库的必要与优点。   ● 熟练驾驭对象库指挥官Object Repository Manager。   ● Object Repository Manager的两个王牌级辅助工具初体验。 1.5.1 引言   如果说,上一章节是彻底摆脱脚本录制的一个良好的开端,是带领读者逐步走向QTP自动化测试项目开发的台阶,那么从本章节开始,读者将跨出巨大的一步,真正开始走进QTP脚本开发的世界。   在QTP这个自动化测试工具中,存在着两种脚本开发模式,第一种开发模式就是从本章节开始要逐步具体、深入、详细讲解的“对象库编程”。当然,本书的一贯原则是“实用原则”,所以无论是在上篇(本章节)还是下篇(1.6章节)都绝对不会详细地讲解在实际项目运用中用不到或很少会用到的知识点。想要精通对象库编程这个开发模式,并驾轻就熟地运用到自动化测试项目中,首当其冲就是要先“精通”QTP的对象库,作者讲的对象库其实是一个总称和概括,从大的方面包含对象库本身的功能、对象库的机制与原理、对象库编程知识、如何在实际项目中运用对象库编程等。   在上面作者主要针对QTP对象库的机制与原理以及对象库本身的功能做深入讲解,而对象库编程的部分则会在下篇中精彩解读。虽然需要掌握那么多知识的确很烦,也需要一个过程,但是作者认为上篇和下篇中的所有内容已经是最精简的了,所以,请读者务必对这些知识点做到“精通”,它们都是经常在实际项目中使用的,可以说是QTP脚本开发的必备技能!如果不能精通QTP的对象库,那一定不会是一名合格的QTP自动化测试工程师! 1.5.2 对象库的出现改写了软件测试历史   QTP的对象库是这个强大的自动化测试工具的核心,也是其精华所在!有了对象库这一整套功能、机制,QTP才可以在那么多的自动化测试工具中脱颖而出!QTP的整套对象库功能与对象识别机制给自动化测试人员带来了前所未有的体验,它的成熟、上手度、良好的使用感受都是市面上其他任何自动化测试工具无法给予的。对象库是QTP在实际项目应用中的枢纽,读者一旦精通了它,会感觉到在项目应用、QTP各项功能中都会和对象库产生必要联系。本章节的内容会结合大量仿真项目的应用来引导读者逐步精通对象库,希望读者能够做到举一反三,细细去品味对象库带来的不同感受(针对之前使用过其他自动化测试工具的读者效果更佳)!   小小的总结一下,可以说QTP在实际项目中的应用开发(特别是自动化测试项目做成功的案例)绝大部分都基于对象库编程模式,所以,对象库的出现改写了自动化测试领域的历史,也推动了这个技术领域走向更高的台阶!另外,先预告下,本章节以及下一章节的篇幅会很大,因为这两个章节的内容是QTP最精华的部分,也是整个第一章的最重要的一块,务必耐心学习。 1.5.3 一个简单的实例介绍对象库原理、机制及操作流程 1.5.3.1 对象模型的老大Object Identification和对象库的暧昧关系   在介绍对象库的原理与机制之前,首先说一下QTP自动化测试的原理,分以下3个步骤。   (1)封装真实被测对象并转化为QTP对象到对象库。   (2)对比对象库里的对象鉴别属性和运行时的真实被测对象的鉴别属性。   (3)对比后如果一致,则说明对象成功匹配并可以继续对该真实被测对象进行后续操作,如果两者不一致则报错,提示为对象无法识别。 我们可以看到,QTP自动化测试的原理其第一步就和对象库产生了联系,具体到底是如何联系起来的呢?其实很简单,在QTP里有测试对象模型这一个概念,它把各种对象都分门别类起来,建立出一个个对象模型,然后用这些对象模型来表示Web页面中的对象,比如WebButton、WebList、WebElement等各种各样的对象。每个对象模型都有一个可以唯一标识对象的关键属性列表,我们将前面这些连起来一块说,就是在添加对象至对象库后,QTP自动建立被添加对象的对象模型,并给出一个默认识别标识。然后,每个对象模型都有一个可以唯一标识对象的属性列表,这个属性列表是可以设置的,它就是Object Identification,先认识一下它,如图1-50所示。 图1-50   如图1-50所示,可以看到这就是Object Identification的界面,开启它的方法是:   QTP上方菜单栏→Tools→Object Identification。   在Object Identification对话框中设置的属性在添加完对象后就可以在对象库中查看到。如果觉得这些默认显示的属性还不够或者有的默认的属性是多余的,可以进行手工设置,看一下实例,通过实例来看最终效果,需要再一次借用百度的搜索框,如图1-51所示。 图1-51   这是一个WebEdit控件,也就是一个WebEdit对象模型,等会作者把这个对象添加到对象库中,添加两次,第一次不改Object Identification中WebEdit对象模型的默认属性,第二次剔除其中一个属性,然后一起看看它在对象库中的属性列表显示情况。先来看默认的情况,如图1-52所示。 图1-52   图1-52中,作者标识了两个不同的区域Mandatory Properties和Assistive Properties。先知道它们的区别,Mandatory Properties就是对象模型的必要属性,这个区域设置的属性在实际被测对象与对象库匹配时,只要有一个属性不一致就报错。Assistive Properties就没有这个规矩了,并且QTP默认所有的对象模型在这个区域中没有任何属性。  继续回到前面所讲,如图1-52中所示,可以看到WebEdit对象模型的默认必要属性有3个:html tag、name、type。接下来,开始添加百度搜索框这个WebEdit对象,并一起来看一下对象库中的属性列表情况,如图1-53所示。 图1-53   我们可以看到,对象库列表中的属性(Description properties)也是3个:type、name、html tag,在Object Identification列表中显示什么,对象库列表就显示什么。   小提示:关于?Test object details?列表中Description properties(描述属性):在?Description properties里定义的所有属性都是必要检查属性,都会和实际运行时对象的属性校验,如果匹配不上则报错,就以图1-53中的name属性举例,如果实际运行时,被测对象的name属性不是wd,则匹配失败,我们还可以根据项目实际情况额外地在列表下添加更多的必要检查属性,也可以筛检一部分,后续章节会有一些类似的实例。   那在Object Identification列表中删除一个属性,对象库列表中会有什么变化呢?会不会仍然一致呢?继续做实验,在Object Identification列表中删除html tag属性,如图1-54所示。 图1-54   可以看到html tag属性已经从Mandatory Properties区域中消失了,关于如何让它消失或者复活,已经在图1-54中明示了。那么,再一次将百度搜索框添加到对象库中看效果,如图1-55所示。 图1-55   实验证明:Object Identification列表中没有的对象库列表中也不会有,如果读者哪一天发现预期结果不正确的时候,请提交缺陷给HP。 不知道读者是否还记得,在“1.3.4 QTP回放机制”那个章节里提到过智能识别这个概念,在Object Identification里也有智能识别的设置按钮,在这里作者介绍下这个功能,并对QTP的智能识别做一些“合理的解释”。我们先来看下Object Identification里的这个智能识别设置功能,如图1-56所示。 图1-56   首先,我们可以看到,在所有的Web对象模型里,每个对象默认是允许智能识别的(而Standard Windows对象模型里的各类对象默认是不支持智能识别的),在这里的智能识别功能和“1.3.4 QTP回放机制”那个章节里提到的智能识别功能区别在于,这里的智能识别功能针对的是对象模型下的某一个具体对象,举个例来说,比如可以让WebEdit对象开启智能识别,但是不让WebCheckBox对象开启,而那个章节中的智能识别是一个总开关,假设这个总开关是打开的,那么例子中的情况就是WebEdit控件可以智能识别,而WebCheckBox控件则不行,那么假设这个总开关是关闭的,则在Object Identification里无论你怎么设置,任何对象控件都不会智能识别,务必别搞乱了!   其次,在Object Identification里,如果某控件开启智能识别(仍然借用WebEdit对象为例),我们还可以对可智能识别的属性进行设置,如前面的图1-56中,点击Configure按钮就可以弹出设置框,设置框效果如图1-57所示。 图1-57   从图1-57中可以看到,已经打开了WebEdit对象模型的智能识别设置窗口,左侧的列表中是这个对象默认的基本智能识别属性,这个列表中的一切属性是即刻生效的(即会首先智能识别这些基本属性),而右侧列表是备用属性,暂时是不会生效的,只有当左侧基本属性列表中的属性全部使用过后,仍然没有识别到的情况下才会生效,QTP会根据备用属性列表中的属性按着设定好的属性顺序来一个个继续智能识别,直到匹配到为止,当然连所有备用属性都不能识别后QTP就会报错了。这里需要注意的是,每个对象模型的默认智能识别属性都是不同的。在做项目的时候,一般使用这些默认的设置即可以应付绝大多数情况,毕竟这些默认设置都是Mercury公司设计出来的,相信他们也是考虑再三的,肯定具备一定道理和权威性。   最后,在了解了Object Identification里的智能识别相关功能和回顾了之前的智能识别功能后,现举一个实例来让读者明白,智能识别这个功能究竟是什么?在项目应用中启用了智能识别后会发生什么?   实例:   先说个基本概念和原理,运用智能识别后,QTP在遇到对象识别出问题的时候,会尝试应用智能识别(Smart Identification),如果智能识别能定位出对象则继续执行脚本,反之则报错并提示错误信息,下面看一下这个示例脚本: Browser("51Testing软件测试网-中国软件测试人的精神家园")._ Page("51Testing软件测试网-中国软件测试人的精神家园")._ Image("51Testing软件测试网").Click   注:以上脚本来源于51Testing软件测试网首页,如图1-58所示。 图1-58  对于“51Testing软件测试网”这个Image对象,QTP默认采用alt、html tag、image type属性来识别对象(见图1-59)。 图1-59   然而,当开发好上述脚本后,假设开发人员又在同一个页面添加了名叫“51Testing软件测试网-老网站”的Image对象(现在用作登录老网站),同时修改了原来的“51Testing软件测试网”对象的alt属性,改成了“51Testing软件测试网-全新网站”。这样当针对这个新版本的页面重新回放上述脚本时,QTP就不能基于alt + html tag + image type的属性组合来识别原先的“51Testing软件测试网”(现在的“51Testing软件测试网-全新网站”)这个Image对象了,因为alt属性已经改了。但是QTP仍然能够正确地识别出这个对象,为什么呢?就是因为这个时候它开启并使用了智能识别功能(Smart Identifation)。   下面的过程描述了QTP应用智能识别来确认原“51Testing软件测试网”这个Image对象的步骤。   (1)根据Image对象默认设置的智能识别定义(默认智能识别属性是html tag),QTP首先从页面中找出所有html tag属性=IMG的对象(图1-55中可以看到html tag的属性就是IMG),前面也讲过了,在Base Filter Properties这个左侧区域中,定义的属性将进行第一轮筛选和过滤。   (2)如果第一轮匹配不到,那么就会在右侧的Optioanl Filter Properties(备选属性区域)中根据定义的属性顺序地进行筛选和过滤,请看以下步骤。   ① QTP会首先检查对象智能识别备选属性的alt属性(默认在备选属性里alt在第一位,但是发现没有等于“51Testing软件测试网”的,因为已经被开发人员改为“51Testing软件测试网-全新网站”了。因此,QTP会忽略这个属性,继续应用下一个备选属性。   ② QTP接着检查对象的image type属性(默认在备选属性里第二位),把image type属性不等于“Image Link”这个值的对象过滤出去(图1-59中可以看到image type的值)。   ③ 然后QTP检查对象的html id属性(默认在备选属性里第三位),发现所有其他的对象的这个属性的值都是“logo”(见图1-60中对应位置)。 图1-60   ④ 紧接着QTP检查对象的name属性(默认在备选属性里第四位),发现有两个对象“51Testing软件测试网-老网站”和“51Testing软件测试网-全新网站”的name属性都等于“Image”(见图1-60中对应位置),因此QTP把其他的对象又一次过滤掉,只剩下这两个对象。   ⑤ 于是QTP再检查剩下两个对象的file name属性,发现只有一个是等于“logo.gif”(见图1-60中对应位置),至此,QTP结束智能识别过程,推断出这个对象是它要找的“51Testing软件测试网”对象,并且点击它。   当然了,如果在上一步仍然不能定位到对象,则还会依此类推继续下去。 图1-60是一个为已经添加至对象库的对象添加必要的识别属性的窗口。需要注意的是,在这个窗口里属性的位置显示是没有次序可言的,或者和Object Identification里智能识别属性设置窗口中是不一一对应的,关于如何打开这个窗口和其他对象库功能的操作见下一个小章节。 1.5.3.2 以一个实例囊括对象库的原理机制与操作流程并揭开整个篇章   已经初步了解了测试对象模型、掌握了Object Identification、QTP智能识别技术,以及对象库的一些原理和机制,内容太多可能有的读者一下子难以消化,在这里再提示一下,演示一个简单的对象库编程的操作流程的实例来启发读者,也为以后更深入地学习打基础。请先看演示需求如表1-3所示。   表1-3            演示需求表   添加对象之前,如图1-61所示。 图1-61   添加完对象后,如图1-62所示。 图1-62   脚本显示代码如下: Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("wd").Set "QTP自动化测试技术领航"   结果:   脚本运行回放完毕后,百度搜索框自动输入了“QTP自动化测试技术领航”,如图1-63所示。 图1-63   这样一个非常简单的实例演示结束了,接着继续更深入地探索QTP的精华对象库以及对象库编程。 1.5.4 对象库基本操作   在上一节中,对对象和对象库有了整体的认知,包括对象库编程在QTP中的价值、对象模型的含义、对象库的一些作用等。在上一节中有很多关于对象库的图例,这些都是作者事先设计好的,但是有些新人读者并不知道这些图例的效果是怎么设计出来的,因为本书还没介绍过对象库功能的操作,那么在本小节中,开始讲一下对象库功能的基本应用与操作,特别要申明下,本小节介绍的对象库指的是QTP对象库的功能意义上的操作,只是一些很单纯的操作方面的东西,就像如何将对象添加到对象库等,而不再像上一节那样指的是“对象库整体”。   首先,我们得知道对象库如何打开,默认有两种方式。   ● QTP上方菜单栏→Resources→Object Repository。   ● 快捷键Ctrl+R(推荐)。   上述步骤操作后,打开的界面如图1-64所示。 图1-64   作者已经在图1-64中以数字标识了对象库功能中必须要掌握的操作,接下来将根据数字的顺序逐个介绍这些知识点(部分知识点配合实例)。   1.Test Objects   我们先来了解对象存放的区域,从QTP 10.0版本开始,对象存放区域被分为两块,HP的意图是将测试对象和检查点对象、输出对象区分开来。Test Objects顾名思义是存放着测试对象(图例效果以及关于如何添加测试对象在第6点,这里暂且略过)。   2.Checkpoint and Output Objects   检查点对象以及输出对象。QTP在10.0以前的版本是没有此区域的,但是感觉这块区域单独划分出来实用性不大,而且经过很多实践证明,Checkpoint and Output Objects在做实际项目时很少会被用到,作者仍然罗列出来的目的是把一些“现状”告知读者,最终是否使用由读者决定。   3.对象库中的Action切换   首先要注意,这里的Action切换只是对象库中的切换,别和对象库外面的Action切换搞混了。在这里,可以通过它查看不同Action下的对象,很好的一个功能,可以非常直观地为对象归类,用户在使用时也可以快速查阅不同Action下所属的对象。同时,当需要添加对象时要注意切换到不同的Action再添加对象,比如当前脚本下有Action1和Action2,如果我们需要在Action2中添加其专属的对象,那就需要把Action切换到Action2后才能添加对象,只有这样对象才会被添加到Action2中。如果仅仅只是建立了Action2,但是添加对象的时候没有切换到Action2(即仍然停留在Action1),此时添加的对象自然是在为Action1添加,很容易忽略这个小细节,请务必注意。   此外还要注意一点,假设脚本调用了外部Action,那么对象库中Action一样可以切换到那个外部的Action,但是只有查看对象的权限,而没有添加、删除、更新、编辑、亮灯等权限(灰显),大家可以看一下效果如图1-65所示。 图1-65   4.对象的剪切、复制、粘贴与删除   这些功能是软件必备的功能。这里作者拿剪切和删除为例告知读者一些该注意的地方(剪切和复制大同小异,无须重复举例)。先看一下图1-66所示。 图1-66   如图1-66所示,现在如果需要将51Testing的搜索框对象剪切到百度的Page页面下,非常简单,只需要点击对象,然后使用图标或者快捷键Ctrl+X,再选中百度的Page进行粘贴即可(这个效果就不附带例图了,大家都能明白)。  这里作者要提及的是如果剪切(或复制)的是父对象,那么粘贴后,其子对象会被一起剪切(或复制)过去,比如现在把百度的Page对象剪切到51Testing的Browser对象下,效果就会如图1-67所示一样,“百度一下,你就知道”这个Page对象以及其下的“wd”子对象被一起带到51Testing的Browser对象下,并且与51Testing下的Page对象平行。 图1-67   删除操作也一样,如果删除父对象,则所有子对象一并被删除,现在删除51Testing的Browser对象试一试,删除后效果如图1-68所示。 图1-68   如图1-68所示,51Testing的Browser对象一旦被删除,其所有内容一并被删除。   5.对象库中对象的查找与替换   QTP的对象库为用户提供了非常快速且方便的GPRS导航系统,它就是Find & Replace功能,快捷键Crtl+F。认识一下这个功能的界面,如图1-69所示。 图1-69   打开这个功能后就会出现图1-69中的界面效果,上半部分是查找功能,下半部分是属性替换功能,在这里就可以将对象的属性值直接替换掉。   6.添加对象   这个功能是罗列的11条里最重要的,没有添加对象也就没有后续功能了。这里需要给读者解释一下,作者本该把最重要的放第1条,但是为了文章的连贯性(如对象库中图标的排列顺序那样,这个按钮也没有排在第1个),所有内容顺序也尽可能与其图标位置相呼应。下面开始添加一个对象。   步骤如下。   第一步,点击Add Object to Local按钮,在点击后会出现一个白色手指。   第二步,拖动白色手指至待添加的对象上,点击鼠标左键。在这里仍然以百度搜索框为例,来看一下点击后的效果图如图1-70所示。 图1-70  在图1-70中可以看到,在选中并点击对象后,还不算是添加对象完毕,QTP此时还会弹出一个对象选择框,用来做最后的选择。即从图1-70中可以看到,虽然只选中了百度搜索框,但同时这个名为“wd”的百度搜索框的两个父对象也会一起显示,如果此时假设选中Page对象,则最后添加进去的对象是这个Page,外加它的父对象Browser。所以,到了这一步只能说快接近完成一个对象添加过程,但是没有真正的结束。最后补充一个概念:当子对象被添加时,其父对象会一起被添加至对象库,而当父对象被添加时,如还有祖父对象,则父对象连同祖父对象一起被添加至对象库,但其子对象不会被添加,新手务必牢记。   第三步,最终确认要添加的对象,确认无误后点击OK按钮。   总结:在完成了以上3个步骤后,对象就添加到对象库了。实际效果如图1-71所示。 图1-71   另外,打个比方,现在需要添加百度首页上的另一个Link对象“贴吧”,它们属于同一个页面的,在添加完后,这个对象就会直接隶属于它的父对象下,如图1-72所示。 图1-72   小提示:当添加对象时,一旦白色手指出现以后就无法再继续页面上的任何操作了,如点击、切换网页、拖动滚轴等,因为只要有任何“左击”的动作,它就会帮你添加对象。假设如果在一个页面上添加对象后,需要切换到另一个Web页面添加另一个对象怎么办?一般的办法就是添加好一个对象后,暂时先不激活白色手指,等网页切换了以后再回到QTP界面切换手指,但这样会繁琐。添加一个、两个对象还好,如果要添加很多对象呢?会一直在页面和QTP界面中来回切换。Mercury公司当然预料到了这个情况,他们提供了一个捕捉模式切换功能。在这里作者先不讲这个功能,也只是做个预告。作者会在介绍到本章节的Object Spy这个重要的知识点时,分享这个实用又贴心的功能,添加对象和Object Spy是共享这一功能的,因为它们都需要捕捉,所以都具备捕捉模式。   这个功能点已经讲完了,但是读者还记得在1.3.1节中请拒绝“录制”,再开始你的实际项目之旅中的1.3.1.2节一些“理论性的社会实际问题”实例吗,作者在末尾段说过要做一个实验来证明以手工添加对象的方式效率远远胜于录制添加的方式。如果已经记不清了,请看下面这段加了引号的原文:   “另外,在添加对象方面上,录制也显得差强人意,因为靠录制的方式添加对象,表面上来看十分快速,但是通过这种方式添加到对象库中的对象,它们的命名都是QTP自动命名的,相当的乱,还是需要一个个去改,而且很多对象还是不需要的,QTP也给自动添加进来了,这样弄下来,效率远不如一开始就老老实实一个个添加对象,并根据规范为它们逐一命名再进行编程呢。”   现在应该回忆起来了吧,作者将借用51Testing论坛的注册页面来做个小小的实验。先来看一下真实环境的界面,首先是进入51Testing软件测试网的首页,并在页面的上方找到“注册”按钮,点击进去,如图1-73所示。 图1-73   在进入了图1-73中的注册页面后,实验就要开始了,我们的业务流程是完成一次注册,所需要使用到的控件作者也已经排列出来了。对于添加对象的方法“是否效率”,暂且先不做对比。在此就先用录制的方式添加这些对象(所谓录制添加的方式就是指在录制这些业务操作的同时,对象会以QTP默认的设定自动添加到对象库),在完成了操作以后,看以下3张相当直观的例图,界面操作图(1-74)、脚本代码图(1-75)、对象库图(1-76)。 简单插一句:界面操作图没什么可说的,是作者刚才在做的事情。   作者有话说:脚本代码图1是录制完毕后,QTP生成的脚本,读者看看这个脚本是否直观?是否简洁?是否优美?作者就此询问了两位QTP初学者,他俩都给出了自己的观点。   ● 甲说:“QTP写的脚本简直太完美了!”   ● 乙说:“感觉不是很直观、精简,应该还可以优化许多!”   作者在这里只会支持“路乙”为什么?请大家看下脚本代码图1(图1-75)中第13、14行和第16、17行代码,这4行代码是对同一个对象控件的操作,读者无论之前是亲自同步操作的或者是看了作者的操作的,请大家讲一下,这个对象控件是刚才操作的哪个对象?相信大部分读者都不好理解。这是很正常的,看着脚本里的“WebEdit("secanswer")”,谁又会在过了1分钟、10分钟、1小时、1天、10天后重新查看脚本(维护脚本)时还牢牢的记住它是一个“验证回答的输入框”对象?当然,在这个超级简单的脚本中,大部分对象控件我们还是比较熟悉的(QTP在录制时为其默认生成的对象名),比如第2行的“WebEdit("username")”一看便知是“用户名输入框”对象,或者会有读者反驳说,自己刚才能迅速反应出是什么对象!那请迅速地告诉自己第20行和第23行的“WebEdit("seccodeverify")”是什么对象?! —界面操作图— 图1-74 —脚本代码图1— 图1-75   在这么简单的一个脚本中就已经出现了2处几秒钟后就会令人“费解”的对象,那更别说十天半个月后的脚本维护工作了,估计那时候打开这个脚本看到这两个对象已经完全不记得它们是什么了!那如何才能有效地避免类似的事情发生呢?作者先不说,请读者继续往下看。 再继续找找这个录制出来的脚本的“茬”!大家有没有发现每行代码都特别的长?长到作者必须要通过换行才能把这张例图的内容完全截取下来,不然肯定有一部分代码无法显示了。截图也就不说啦,就说QTP界面,估计这么长的代码也是无法在界面中全部显示的吧?要通过滚动条才能看到末尾的代码。   现在可以开始解释了。QTP录制出来的脚本真是不太直观!那如何应对?做到直观、简洁并使代码显得更加优美?那就是替录制生成的对象“整下容”,个性化定制自己觉得舒服的名字,比如在这里完全可以将“WebEdit("secanswer")”改成“WebEdit("验证回答")”,将“Browser("51Testing软件测试论坛 软件测试?|")”简化成“Browser("51Testing软件测试论坛")”,将“Page("51Testing软件测试论坛 软件测试?|")”简化成“Page("51Testing软件测试论坛")”等。这样代码就直观、很精炼了。另外需要注意的是,对象类型如WebEdit是不可以更改的,只能修改其括号内的Name。   不过,不能在脚本里直接改,必须要在对象库里改,否则运行的时候对象库里的对象和脚本里所描述的对象就不匹配了,会导致报错!那就进入对象库,先看看“对象库图1”图1-76所示。 图1-76   先不说怎么为对象改名,当务之急是要继续找对象库的“茬”!图1-76中的这些对象就是在录制时同步添加进对象库的,是脚本里所有涉及的对象。我们可以看到,在这个对象库中呈现出,有的对象默认以中文命名、有的对象却又默认以英文命名,而有的对象的默认命名不够精简。其实这些“茬”刚才在“脚本代码图1”中是同样存在的,只是作者有意将其留到这里。作者找出这个“茬”也不是没根据的,同样地请读者看这份对象库列表,你能迅速而准确地告诉自己每个对象符号代表着哪个对象吗?结论很明确,“茬”也找的差不多了,解决办法前面也讲过了,就是通过先进的科技手段为这些对象“整容”!怎么整?下面作者为读者做个示范,将图1-76中最下面那个WebEdit对象“username”弄成“用户名”。   步骤如下。   第一步,选中“username”这个对象后,将光标定位到右上角的“Name编辑框”,输入“用户名”,如图1-77所示。 图1-77   第二步,随意点击一下界面任何位置,目的是为了完成Name的变更,相当于点击“确定”的功效,“整容”后的效果如图1-78所示。 图1-78   简单两步,“整容”完毕!可以很清楚地从图1-78中看到“username”,从此以后将以更易理解的“用户名”展现给大家。另外再来看看脚本中的第2行代码,发现对象在对象库中更改名称以后,在脚本中也会自动更新,可以看到在脚本中原先的“WebEdit("username")”同步更新成了“WebEdit("用户名")”。  注:这个小功能还是比较常用的!   接下来展现一下作者已经全部修改好的对象库与脚本,见脚本代码图2(图1-79)、对象库图2(图1-80),看这样是不是优化多了! —脚本代码图2— 图1-79 —对象库图2— 图1-80   这样处理过的时间长了再回来看脚本和对象库也是一目了然的。但是,这些都是作者之后的补救工作,是后面才改的,这样也会存在不少风险,有时候稍不留神就会改错,要知道对象改错了是会影响脚本的。同样也相当浪费时间,还要在对象库中一个个去识别这些陌生的对象,那还不如在一开始添加的时候就一边添加一边改名呢,这样就绝对不会遗漏和弄错,同样也节省了很多时间!   最后,我们来做个设想,这个业务是非常简单的,对象控件也比较少,我们还能勉强改过来,那如果碰到业务相当复杂、对象控件相当多的情况呢?还能那么轻易地修改吗?读者还可以跟着作者做一个实验,用手表或计时器看看手工添加对象速度快还是录制后修改来的快!前提是效果必须不能比“脚本代码图2”和“对象库图2”中的效果差,至少也得保持一致。顺便提醒一下:对象越多、业务越复杂,“手工添加”越能超过“录制后修改”!   7.对象更新   当版本更新后,原先的脚本由于对象变化导致无法顺利运行,此时就可以使用这个功能进行更新,操作步骤和添加对象的3个步骤完全一样,只是按钮不同,这里不再重复。另外需要注意的是,假设在对象库中选中的是WebEdit控件,但是想更新成Link控件,则会报错,如图1-81所示。 图1-81   虽然无法更新不同种类的对象控件,但是同种类的控件可以随意更新,切记!另外,作者在做项目时,也碰到过一个现象,就是对象其实没有改变,但是仍然由于对象识别不了而脚本报错了,此时,也可以使用对象更新功能,更新一下就好了。一直不确定为什么会如此,但是的确很实用,也算是作者的一个经验分享吧。 8.对象闪烁(亮灯)   Highlight in Application功能可以使对象在程序或者Web网页上闪烁,非常的显眼、高调!在实际项目中,这个功能使用率很高,大多出现在调试脚本的时候。作者就经常在脚本出现错误时(由对象不能识别引发的错误),首先先“亮灯”,看看能否定位到对象,如果能“亮灯”就说明对象本身是能识别的,可能是因为其他原因综合导致对象假象性的无法识别,当然如果不“亮灯”,那就很明显是对象属性变更导致的识别不了对象。好好地利用“亮灯”技术可以给脚本调试带来很多便利。   脚本错误的原因可以说是千变万化,调试脚本、定位错误绝对是门技术活,在这里就可以靠“亮灯”来一步步定位,排除可能性。反正,只要能成功定位脚本错误的具体原因,无论是什么方法,都是好方法,在今后的内容中,作者也会尽量多列举些这方面的内容。   补充一个小知识点,这个“亮灯”功能有一个Highlight的隐藏方法,如果将代码写入脚本,那么当执行完这句代码以后,本句代码的对象在程序或Web页面上同样会进行闪烁,效果和点击图标是一样的,在某些特殊情况下脚本调试时会运用到。举个例子,下面这句代码就会使“wd”这个WebEdit对象闪烁: Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("wd").Highlight   9.初步认识下Object Spy   在打开对象库以后,也有一个Object Spy功能,这个功能和另一个QTP内置Object Spy是一模一样的。Mercury把这个功能也放进对象库应该是为了让用户使用更方便。在这里,读者暂时先知道对象库里也有一个就可以了,后面的小节中会重点讲解Object Spy相关知识。   10.Associate Repositories   这个功能的中文名叫副对象库,形象点可以把它看成一名开长途车的“副驾驶员”,当主驾驶累了副驾驶可以顶上继续长途跋涉。这个副对象库功能在实际项目应用中经常会被使用到,可以根据具体的业务情况事先封装好一些项目要用到的对象,并组合成一个对象库文件,在需要时就可以调用这些对象库文件到对象库中,成为一个副对象库。不过使用副对象库功能会有一个弊端,就是在加载后会产生很多ObjectRepository.bdb文件,读者需要了解一下。那么接下来,让我们先来看一下具体是如何调用的,图1-82就是打开副对象库后的窗口界面。 图1-82   图1-82就是打开副对象库后的效果,界面非常直观,操作也相当方便,作者在这里就不对如何操作做过多的阐述了。界面中一共有3块区域,已经在图中标识了,接下来,让我们一起动手做一个完整的实例。   预备工作。   (1)事先准备两个对象库文件作为副对象库,分别命名为link.tsr以及edit.tsr(其中link.tsr内置“百度”首页上的“视频”这个Link对象,而edit.tsr内置“百度搜索框”)。   (2)启动QTP。   (3)准备两个Action(除默认的Action1外再新增一个Action2)。   (4)开启对象库并将库中的Action切换至Action2。   (5)进入“百度”首页,将“百度一下”这个WebButton测试控件添加至对象库。   注:暂时不用管.tsr对象库文件是如何生成的,在本小节最后的补充知识点中,以及后续介绍Object Repository Manager那个小节中都会讲到。 要求&完成目标。   (1)不得再添加任何对象控件。   (2)使用已有资源并利用副对象库功能完成以下业务。   ● 在Action1中点击“视频”链接。   ● 在Action2中输入“QTP自动化测试技术领航”并点击“百度一下”。   (3)Action2很有可能以后会添加新业务,所以,需要在Action2中包含所有已知业务所需的对象,即“视频”这个Link对象也需要存在。   (4)无须写脚本代码,只需相应地设计对象库即可。   首先回顾一下刚学过的知识(3.对象库中的Action切换),在上述描写的预备工作中的第4点中我们将Action切换至Action2了,所以在完成了第5点后,此时“百度一下”这个测试控件被添加在对象库中的Action2里,而Action1里是没有这个对象的。   接下来开始“审题”并依次进行解题,请看以下步骤。   第一步,不得再添加任何对象控件。   本点要求不得再添加任何对象控件,所以,必须也只能使用副对象功能才可能完成任务。   第二步,在Action1中点击“视频”链接。   要完成此步则必须将“视频”这个对象添加到对象库中,所以,需要将link.tsr文件导入副对象库,如图1-83所示。 图1-83   我们可以看到link.tsr文件已经被导入到副对象库中(路径自定)。但是,此时尚未完全成功,还需要一步,就是将这个link.tsr文件具体分配到Action的操作。在图1-83中可以看到,在可用Action区域中一共存在两个可用Action,这两个Action就是在脚本中建立的,脚本Test中有多少个Action,这个区域就有多少个Action。   小提示:什么是脚本Test?请先看图1-84和图1-85所示。 图1-84 图1-85   我们来看上面两张图,通常建立的最基础的脚本就是Test(图1-84),在Test Flow中,Test下会以列表形式显示所有Action及其执行逻辑与顺序(图1-85)。 根据业务要求,我们把Action1拖至右侧的已用Action区域(Action2暂时还没用不动它),如图1-86所示。 图1-86   在图1-86所示的步骤完成后,点击OK按钮,整个操作完成,link.tsr文件中的对象也将成功载入到对象库的Action1中,如图1-87所示。 图1-87   如图1-87,有了对象,就可以开始对象库编程了,不过需要注意的是,从副对象库中引用的对象是灰色的,无法编辑的,如果想编辑对象,则必须先将其变成本地对象,这个在这里就不多讲了,后面会在“11.对象库的切换”中讲到。   第三步,在Action2中输入“QTP自动化测试技术领航”并点击“百度一下”。   这个步骤很有意思,这里需要先输入再点击这两个步骤组合完成。我们在预备工作中已经为Action2添加了“点击”所需的对象了(图1-88),现在就需要通过导入副对象库的方式组合进来。我们把“输入”所需的对象给添加进来,步骤不再重复,直接忽视,最终效果如图1-89所示。 图1-88 图1-89   下面就可以根据业务要求正确地在脚本的Action2中进行对象库编程了。   第四步,Action2很有可能以后会添加新业务,所以需要在Action2中包含所有已知业务所需的对象,即“视频”这个Link对象也需要存在。 这一步唯一要做的就是把“视频”对象同样添加到Action2中,以备不时需要。“视频”对象在?link.tsr?文件中,在添加前可以看到目前该文件中的对象没有出现在?Action2中(图1-89),就是因为在设置时没将Action2从可用设成已用(见图1-26)。所以,只需要将Action2“右移”就可以了,步骤直接忽略,最终效果如图1-90所示。 图1-90   在“视频”对象被添加进Action2后,目标也就完成了。不过相信细心的读者会发现一个问题,大家先把书翻到前面看图1-87,在link.tsr文件被加载后,Action1中的所有对象都是灰色显示的,这代表这些对象是从副对象库中加载的,那为什么到了Action2中,只有“视频”这个子对象灰色而其父对象与祖父对象不灰色(请看QTP当前环境,也可看图1-90)?其实道理很简单,“视频”、“百度一下”、“wd(搜索框)”都是子对象,它们的祖父对象Browser以及父对象Page是3兄弟共有的。之前已经完整操作过一遍了,添加“百度一下”是添加到本地对象库的,所以,其父对象Page以及祖父对象Browser也属于“本地对象”,“本地对象”是不会灰色且可编辑的,这个毋庸置疑。那么在“视频”对象加载进来后,由于它的祖父对象和父对象与Action2中的完全吻合,QTP会在吻合的时候进行合并,“户主”还是原来的“本地对象”,所以“视频”对象只能自己灰色,QTP绝对不会允许它改变它祖父和父亲的“户籍”,这就是QTP的一个规则,虽然不是很重要,但是还是需要为读者揭开这个谜团。其实在之前加载“百度搜索框”的步骤时就已经出现了这个情况,“wd”灰色而其祖父Browser与父亲Page不灰色。   到这里为止,整个实例过程讲解全部结束,这个实例还是比较细致的,在分步讲解过程中,也扩展穿插了一些小知识点,希望读者能够完全掌握“副对象库”的使用方法并做到融会贯通!另外需要注意,点击QTP的上方菜单栏Resources→Associate Repositories,同样能够打开副对象库,入口不同但是结果是相同的。   在结束第10小点之前,作者还要分享一句代码,它就是本书中会陆续讲到的3大实时动态加载的第1个之“动态对象库加载”,代码如下所示: RepositoriesCollection.Add "D:\QTP自动化测试技术领航\link.tsr"   这句代码写在脚本中,一旦被执行到以后会触发加载对象库文件使其成为副对象库的事件,效果和手工添加副对象库完全一致,只是没有了选择Action并加载的步骤,因为没必要,把这句代码写在Action1中,则说明加载到Action1中;添加到Action2中,则代码加载到Action2中,依此类推。通常,把3大实时动态加载的代码写在脚本的最前面几行,另外需要注意的是,3大实时动态加载的生命周期都很短,脚本执行结束它们也就结束。下面提供一个用VBScript后台语言动态调用副对象库的函数供读者参考: Public Function AddObjectRepository(objectrepositoryname)       Dim Pos      '判断:如果已存在".tsr"后缀名则直接使用该文件路径,如果不存在则添加后缀名      If instr(objectrepositoryname,".tsr") > 0 Then      RepPath = objectrepositoryname      else      RepPath = objectrepositoryname & ".tsr"      End If        '初始化:如果存在残留的副对象库则删除      RepositoriesCollection.RemoveAll()      '添加副对象库      RepositoriesCollection.Add(RepPath)         '添加后的验证:查找副对象库并将值返回给变量,如果存在返回1      Pos = RepositoriesCollection.Find(RepPath)       MsgBox Pos      '返回的值如果不等于1代表不存在则报错      If Pos <> 1 Then      MsgBox "找不到副对象库!"      End If   End Function '调用该函数 Call AddObjectRepository("D:\QTP自动化测试技术领航\link.tsr") 11.对象库中对象类型的分类与切换   这个功能相对比较简单,其更大的意义在于方便用户操作,可使对象库的类型根据实际操作进行准确而快速地分类,它是以下拉框形式出现的,默认情况下只有All Objects和Local Objects两个类型,含义就如同其字面意思,不多做解释了。   接下来做一个实验,首先需要有低碳的理念,所以原材料仍然复用之前的link.tsr文件,在导入副对象库并应用到Action后,再添加一个“百度搜索框”(本地添加)。在这些准备工作全部完成以后,可以看到对象库中又多了一种对象类型link.tsr,这个其实就是副对象(也可叫作辅助对象),以导入到副对象库的对象库文件名显示,可以有多个不同的副对象,它们的命名也各不相同,但是都属于同一个种类。分别来看一下各种对象类型的实际图例,如图1-91、图1-92、图1-93所示。 图1-91   如图1-91所示。All Objects下显示了所有的对象,其中副对象“视频”以灰色显示,但是其父对象Page和祖父对象Browser没有灰色,被转化成为本地对象,因为它们另外还有个身为本地对象的后裔“wd”,这个知识点在前面的内容中已经讲解过了,希望在这里能起到一个复习的作用。 图1-92   如图1-92所示,Local Objects下正确显示了手工添加的本地对象,副对象是绝对不会在这里出现的,如果出现请将Bug提交给HP公司! 图1-93   如图1-93所示,可以看到所有的对象都是灰色的,这就足以证明它们是通过特殊渠道进来的,这里会显示link.tsr文件下的所有对象。除此之外其他任何本地对象都被排除在外,如果有多个副对象被添加进对象库,它们也只会各司其职,绝对不会发生link.tsr中出现edit.tsr中的对象的错乱情况。   在这里还需要提到一个功能,可以看到,图1-93中的这些对象都是不能编辑的。在讲“副对象库”的时候就讲过,由副对象库导入的对象是无法编辑的,要编辑的话就必须使其先“转职”成本地对象库(即主对象库)。怎么“转职”呢?很简单,在你需要“转职”的对象上点击右键后,再点击Copy to Local即可,现在要把“视 频”这个对象“转正”了,来看下面的操作实例,如图1-94所示。 图1-94 请注意图1-94中的两个方框位置,先来看左下角的这个,Copy to Local是灰色的,这是怎么回事?刚才还不是说副对象可以转成本地对象的吗?那为什么这个功能灰色了?请看右上角的方框,此时,对象库的对象类型是link.tsr,也就是副对象,而要使对象“转职”必须切换到All Objects下进行,操作实例如图1-95所示。 图1-95   同样先注意两个方框位置,这个时候可以清楚地看到Copy to Local可以点击了。点击后即宣告“转职”成功,需要注意的是在“转职”后,原先的副对象库文件是绝对不会受到影响的,因为“转职”只起到了Copy作用,它并没有改变文件自身内容的本领。另外还需要注意一点,“转职”跟复制、剪切、删除对象是相反的,父对象一旦“转职”了,祖父对象也受影响跟着“转职”而不会制约子对象,复制、剪切、删除则是父对象制约子对象,而祖父对象不受任何制约,正好相反,读者可以多加尝试!   知识点补充   以上的11点全部讲解完了,除此之外,作者再介绍一项图中没有用数字标识出来的对象库导出(替换)功能,在项目应用中也时常会使用到,先看这个功能出自于哪里,如图1-96所示。 图1-96   上图1-96所示就是这个功能的具体位置,File菜单下有3个子菜单,其中Close就是关闭对象库,这个太简单了,主要说一下“Export Local Objects”和“Export and Replace Local Objects”,这两个功能的共同点是,它们都可以导出对象,前者就是最单纯的导出本地对象,而后者稍许复杂点,除了导出本地对象以外还有一个“代替”的附加功能,这个附加功能的实际效果是什么?让我们一起来看两个实例。第1个实例就是最简单的导出对象,操作实例图1-97所示。 图1-97 通过图1-97中的操作,就可以保存一个对象库文件到指定文件夹中了,之前一直用的link.tsr就是这么来的,这个是最简单的,接下来主要讲讲后者,在讲之前先做一下准备工作。很简单,在新脚本里添加一个“百度一下”按钮就可以了。继续,后者其实就多了一个“替换”的附加功能,作者起先误以为是保存文件的时候,如果命名相同则会覆盖,保留下新的文件内容,但是仔细想想就觉得不对,这好像有点多此一举,因为Mercury并不需要单独开发这个功能,Windows系统现成的就有这个功能了,那这里的替换到底是什么?作者其实也不大明白……那我们现在怎么办?还记得1.2中学到的知识吗?通过帮助文档“F1”找到想要的答案,在输入关键字“Export and Replace Local Objects”后,“F1”终于揭开了真相,如图1-98所示。 图1-98   如图1-98所示,帮助文档已经写的非常清楚,原来选择Export and Replace Local Objects后,这些对象就会自动替换成副对象(也叫辅助对象),以副对象的身份出现,其本地对象的身份将被剥夺,原来这里的替换含义是将本地对象替换成副对象,实际效果就是导出这些对象并自动替换成副对象来使用,那么将其导出,就命名为“实例2.tsr”,并且来验证一下到底正确不正确。需要验证两点:第一点,副对象是灰色的,那我们来看究竟有没有达到预期结果(读者也可以自己在本地环境上同步试验),如图1-99所示。 图1-99   第一点通过了,那么接下来验证第二点,副对象库里是否存在刚才被导出的文件,打开副对象库,如图1-100所示。 图1-100   我们可以看到,在副对象库中的确存在“实例2.tsr”这个文件,并且当前Action自动会从可用区域移至已用区域,这也就意味着该文件中的对象已经在履行副对象的使命了。至此,试验结束,验证通过,和“F1”里描述的毫无差错。通过这两个实例,相信读者应该可以理解导出对象的两个不同的概念及其运用了。特别是后者,如果能在实际项目中举一反三且灵活运用的话,一定可以化繁为简,并很大程度上提高对象维护的效率。   注:本小节的内容针对对象库功能的基本操作,但是都是非常具有实用的,扩展操作或高级操作如共享对象库、对象库合并等知识在后续小节中讲解。 1.5.5 Object Spy让对象无处藏身 1.5.5.1 结交新朋友—侦探Jack   做基于界面的功能自动化测试,其归根到底就是对界面上的对象控件做文章。想要掌控这些“五花八门”的对象控件,就一定要深入地去认识它们,只有掌握了它们的内部结构,自动化测试工程师才能把测试工作做起来。内部结构是什么?其实就是一个个(大批量)的对象属性,正是利用这些属性才能控制这些对象控件,以便使用它们做每一件事。不过,对象控件不是万能的,它们都有自己的业务范围,所以,它们只对范围内的有效!   那么,该如何去探知对象控件的内部结构呢?有两种办法:第一种,自己识别对象控件的内部结构!如果第一种办法不适合你或者你没法实现。那么第二种办法就是获得一种探测器工具,用它可以轻松探测各类对象控件的属性。在这一小节,作者将讲解第二种办法。   市面上,有很多自动化测试工具内置这样一个探测器,因为只有探测到了对象控件的内部属性,才能继续将自动化测试做下去。如果没有内置的探测器,也一定要去寻找一个独立的外部探测器来配合使用。QTP直接提供了一个重量级的探测仪,它就是Object Spy,它一定会是你自动化测试旅途的“贴身伴侣”,让我们来认识一下这位新朋友,有3种方式可以调用它。   (1)QTP上方菜单栏→Tools→Object Spy。   (2)QTP默认显示工具栏中点击Object Spy的图标,如图1-101所示(推荐)。 图1-101   (3)打开对象库,在对象库中找到“侦探Jack”,然后点击它(推荐)。   3种方式中有两种是推荐使用方法。如果没有打开对象库,那就直接点击工具栏中的图标,如果对象库已被打开,那么就可以点击里面的图标(前面的内容中已提到过,点击对象库中的Spy图标一样能调用它),做到灵活应用!细心的读者会问,为什么不支持快捷键?这个问题请咨询HP,至少目前暂时还没有快捷键。那么接下来,有请我们的“侦探Jack”,如图1-102所示。 图1-102   如图1-102所示,已经见到了“Jack先生”的庐山真面目,那么接下来作者用它探测一个对象,然后对这个界面做一些说明(如何探测先不讲,后面就讲。另外,本说明只针对新人,已经会的朋友请体谅下作者要照顾到每一位读者,暂时跳过即可),被探测的对象是百度的搜索框。那么就让新人和作者一起来看界面说明图,如图1-103所示。 图1-103 界面详细说明。   1.探测手指   点击该手指后会出现一个白色的手指,就和添加对象一样,作用就是选择需要探测的对象,在下一个小节中,作者会扩充一个知识点“捕获对象时的模式切换”。   2.Keep Object Spy on top while spying   探测时保持探测器置顶,这个就看个人习惯了,默认是勾选上的,也就是总是保持置顶的,如果不习惯这种方式,取消勾选就可以了,在这里还是推荐大家采用默认的形式。   3.显示对象的区域   在使用探测手指后,这块显示区域会显示Object Spy探测到的对象及其父对象、祖父对象。默认是停格在被探测的对象上,如果需要查看它的父对象或祖父对象,只需点击即可。在这里能看到“百度搜索框”被探测到了,它就是“WebEdit:wd”。   4.Native Properties和Identification Properties的切换   这是一个重点,也是学习Spy探测器最难的地方!这里所牵涉的内容不只包括本小节,还包括本小节以外的另外几个小节。在这里先大致介绍一下,以后还会多次碰到。首先,作者在这里做一下翻译工作,Native Properties?=?本地的属性,Identification Properties?=?鉴别属性,这两个名词一定看不懂。让作者来替广大新人读者解惑,本地属性就把它看作一个对象控件的自身接口的属性,而鉴别属性它是QTP默认为该对象控件封装的属性。本地的属性相当多,是封装属性的数倍!下面图1-104所示是切换到封装属性的情况,显示的都是“百度搜索框”的封装属性: 图1-104   图1-106就是“百度搜索框”的一些封装的属性。什么叫封装?这个在这里就不多解释了,因为和本书无关,不过在这里可以告诉你,QTP,把“百度搜索框”封装成一个?WebEdit?控件,依据在哪?因为?Class Name?=?WebEdit(位置在图1-104中已标注)。Class Name就是“百度搜索框”众多封装属性中的一个,读者可以重新选择到本地属性里去看看有没有Class Name,答案是没有!至于这些封装属性是怎么来的呢?答案是?QTP?自己给封装好的。所以,关于这些封装属性的秘密,在下一章节中会讲到,敬请期待吧!   言归正传。两种属性都是在实际的运用中经常会被用到的,调用不同的属性方法的形式也是不同的,两个概念上的介绍基本就到这里了。   后者是Identification Properties,以后会牵涉到对象库编程中的两个重大概念Test Objects(测试对象,TO)和Run-time Objects(运行时对象,RO),在为TO或RO进行编码的时候使用的就是QTP自己封装的这些对象属性。关于前者,也就是Native Properties有其独特的调用方法。   5.Properties和Operations的切换   这里以切换选项卡的形式来查看对象的属性或查看关于该对象控件的操作的一些方法,默认选项卡是查看对象属性的。   6.对象属性&对象操作的详细表   本条和第5条是关联的,在这个区域能查询到对象的属性(包括本地的属性和鉴别属性)及其属性值或对象控件的一些方法,在图1-104中,我们可以看到对象的具体属性显示效果,那么接下来,把选项卡切换到“对象操作”效率效果,如图1-105所示。 图1-105   在如图1-105中,可以查看到关于“百度搜索框”的一些可执行的操作及其方法,如CheckProperty、Click、Drop、ChildObjects及Exist等。 7.文字显示区域   别小看这一块小小的区域,在实际项目中有实际的用处的,现举两个例子,先来看一下两张图(图1-106和图1-107)。 图1-106 图1-107   首先需要说明一下,在图1-106上鼠标箭头停格在default value这个属性上并点击,而图1-107则是停格在属性abs_x的值606上并点击,由于鼠标箭头无法被截在图中,所以只能在图中标记。   为什么说这个小小的区域很实用呢?第一个例子大家请看图1-106,假设需要把default value这个属性写到脚本的某行中去。那么此时有两种办法,第一种就是手写,一个一个字母的拼写,显然这种方法效率比较低,而且容易拼写错。所以推荐第二种方法,就是去点击表中的default value,点击后,在文字显示区域就会显示default value了,然后再复制、粘贴到脚本中,这样效率就大大提高而且绝对不会出错了。这个default value的例子还是比较经典的,请注意当中是有一个空格的,用这种的话就保证不会漏掉这个空格了。   然后看一下第二个例子,在这里不光可以点击“属性”,还可以点击“属性的值”,同样可以出现在文字显示区域中,比如图1-107中的606。除此之外,Operations选项卡下的内容同样能够点击、显示,只不过Operations选项卡下的内容只是看看就行了,是没有实际性用途的。   8.具体描述区域   这个区域中会显示一些QTP自带的描述信息。细心的读者应该会发现,Properties选项卡下这块区域是没有什么描述信息的,这说明HP公司并没有给各类属性添加描述。但是在切换到Operations选项卡后,可以发现HP公司给所有QTP自行封装的方法进行了描述,比如Click方法(图1-108),而自身接口的一些操作方法都是没有描述的,同样如Click方法(图1-109)。 图1-108 图1-109   这个知识点大家只需要了解即可,即使QTP自带的封装方法有一些描述,也没有必要去看,因为我们有“F1”这个好老师。 1.5.5.2 捕获对象时的模式切换   在讲解对象库功能之一“6.添加对象”的时候,作者分享了“小提示9”,读者可以重新翻回到那部分内容。当时提出了一个问题“假设在一个页面上,添加对象后需要切换到另一个Web页面添加另一个对象怎么办?”,最后作者给出了最好的解决方案“在添加对象时,QTP提供了捕捉模式切换功能”。但是作者并没有继续深入下去,因为添加对象时的模式切换是和本小节要讲到的Object Spy捕捉对象的模式切换是一致的,现在,可以好好学习一下这个技巧了,这个知识点在今后的实际项目运用中经常会被使用到。   作者先举一个简单的例子,还是借用“百度”为例,当我们打开百度进入到百度首页时,会发现有个搜索框,这个搜索框是有讲究的,它支持AJAX技术,直白点说也就是模糊匹配下拉框功能,那么在这里我们将通过这个功能来进入本小节的主题。   实验开始了,在搜索框中输入“QTP”,可以看到此搜索框下面会自动出现一些模糊匹配“QTP”的关键词,如图1-110所示。 图1-110   假设需要在这功能上做自动化测试,首先需要抓取到这些对象(模糊匹配出来的关键词)。那让我们试着使用“侦探Jack”去抓取它们!但是,我们会发现在抓取的时候事情出现了。你会发现“侦探Jack”根本无法抓取到图中的这些模糊匹配出来的对象,当点击探测手指以后就相当于切换了窗口,再切回来的话模糊匹配下拉框就必定会消失,这样就导致根本无法正常捕捉到这些需要获取的对象……这块功能的自动化测试就无法进行下去吗?当然不是!其实这块功能的自动化测试是非常简单的,问题就出在无法捕获到这些模糊匹配的对象控件。为了解决这个问题。那么在这里为读者介绍的模式切换就起到了决定性的作用,我们完全可以不需要切换百度窗口而直接获取到模糊匹配下拉框里的对象,这就是模式切换的优势,接下来,会详细讲解本小节的主题—捕获对象时的模式切换(当然也包括了添加对象时)。   通常使用过QTP的读者都会发现,当使用Spy或者对象库捕获对象时,一旦点击白色手指后就只能抓取对象而无法进行其他任何操作。但其实当在点击了白色手指之后,仍然可以通过两种模式切换操作,使得切换后可以停止对控件的抓取,并还可以对被测对象进行操作。那么,看一下是哪两种模式。   ● 半操作模式—CTRL键   支持点击。   ● 全操作模式—CTRL+ALT键  支持点击和键盘输入。   1.半操作模式详解   首先进入百度首页,当使用Spy并点击白色手指后,可以看到鼠标光标是手指的状态,此时也就是捕获对象的状态。在这个时侯是不可以做任何操作的,只能抓取想要的对象,而当按住CTRL键并保持不放开,就可以做点击动作了,如同正常操作网页,也包括拉动滚动条等。与此同时,鼠标光标也会从“小手指”变回原来的鼠标箭头,当放开CTRL键时,可以看到鼠标光标又会从箭头恢复到“小手指”,也就等于恢复到抓取对象的模式。   2.全操作模式详解   全操作模式就是支持任何操作,包括点击和键盘输入。当处在抓取对象模式的情况下按住CTRL键不放,然后再按下ALT键,紧接着先松开CTRL键再松开ALT键,这样就已经成功切换到全操作模式了。假设在此模式下在百度搜索框中进行输入操作,输入“quicktest”(图1-111),在输入后百度搜索框会弹出模糊匹配下拉框,这个时候可以通过点击CTRL键,然后松开从而达到切换回抓取对象模式的目的,此时模糊匹配下拉框也就不会消失了,这样就能够对模糊匹配下拉框对象控件进行“抓取”,也就成功解决了之前无法抓取到该对象的难题(见图1-112)。有了模式切换,就算再“狡猾”的对象控件也照样会被一网打尽。 图1-111 图1-112 小技巧:当在使用Spy抓取对象时,如果遇到需要抓取的对象恰好被Spy窗口挡住的情况(图1-113),可以拖动Spy使其不会将需要添加的对象挡住(图1-114)。 图1-113 图1-114   整个Object Spy操作运用就介绍到这里了,对于自动化测试工程师来说,“侦探Jack”起着至关重要的作用,往往需要去探测一些对象控件的属性并利用它们,这就全要靠我们今天认识的这位“新朋友”了。特别是新人,他们往往对对象控件的“习性”不够熟悉,所以更加需要依赖于Spy,直到对每个对象都非常熟悉以后,可以逐步摆脱Spy来节省时间。 1.5.6  对象库的最高指挥官(Object Repository Manager) 1.5.6.1  使用公共对象库更有利于项目管理   对于使用QTP的自动化测试项目来说,其对象库的管理在整个自动化测试过程中占有非常重要的地位。特别是对于一个大型的应用系统,往往界面上的对象多而杂,所以拥有一个统一、规范、直观、有序的对象库将非常有利于脚本的快速开发以及团队成员间的协作。在前面的小节中已经深入介绍了Object Repository,不过这只能说已经掌握了单兵作战的技巧,在真实的自动化测试项目运用中,我们需要的是整个团队的协作,只有这样自动化测试项目最终才能成功。如果选择对象库编程来完成所在的项目,那么如何管理好对象库将是一门很大的学问和最当务之急要研究、探讨的事情。   有幸的是,QTP提供了一套管理对象库的功能,由此也就产生了公共对象库的概念。那么,什么叫公共对象库呢?举个最简单的例子,假设系统中有3个模块,分别由3名自动化测试工程师负责,此3人在完成自己模块的脚本开发后,开始进行脚本联调,但是他们发现自己模块的脚本单独运行都非常完美而却在集成完毕以后发生了致命的错误,没有一个组合后的业务脚本能够顺利的通过。最后他们发现,导致如此的原因是因为,他们没有一个统一的对象库,比如有一个公共对象Browser(“browser”),3个模块都具备这么一个对象,但是3名工程师分别把它写成了Browser(“browser1”)、Browser(“browser2”)、Browser(“browser3”),对象的命名规范都没有统一,那最终集成到一起以后怎么可能没有问题呢?要想防止类似的问题发生,就必须管理好对象库,将凌乱而琐碎的对象进行统一管理,并集成到一个对象库中,所有工程师只在这一个对象库上进行脚本开发工作,因此也就产生了公共对象库这个名词!当然,作者也并不是说必须所有的对象全部集合在一个对象库中,但是至少(再次强调“至少”这两个字)模块间共有的对象必须提炼出来并统一命名,然后封装成一个供所有团队成员共享的公共对象库。这个公共对象库是不能随意变更的,必须有专人进行严格控制及维护,这个在本书的第一章中就已经讲过。另外,关于如何调用公共对象库,请读者复习“1.5.4  对象库基本操作”,这个小节中的第10点“Associate Repositories”,在这里就不再对这些知识点做重复性的阐述了。现在去认识对象库的最高指挥官Object Repository Manager吧。   首先,召唤Object Repository Manager的方法不像召唤Object Spy那样的多,既没有快捷键,也没有什么直接可以点击的图标,唯一的入口就是。   QTP上方菜单栏→Resources→Object Repository Manager。   点击进入后,界面效果如图1-115所示。 图1-115   从图1-115中可以看到Object Repository Manager中的很多可用功能和前面已经介绍过的Object Repository中的完全是同一个功能,在图1-115中,作者只是特别标注了个别几个。这些既然前面都已经介绍过,在这里就略过了。接下来,结合一个实例来介绍Enable Editing这个功能(图1-115中已标注位置)以及整个对象库管理的大致操作流程。 Object Repository Manager的实例1。   Step 1,依次选择File→Open→link.tsr。   打开之前使用过的素材<一个对象库文件>,打开后的效果如图1-116所示。 图1-116   在1-116图中,读者需要注意到3处地方,Read only、Add按钮灰色以及所有对象的灰色,由此可以得出结论:原来在Object Repository Manager中打开一个对象库文件后是不能进行编辑修改的,也不可以添加新的对象进来,只可以单纯地“看”!那这样的话,这个对象库管理机制还有什么实质意义呢?难道想要添加或者修改亦或者删除一些对象都不行吗?   当然不是!这只是默认的一个情况,在Object Repository Manager中提供了一个按钮,它就是Enable Editing,点击它以后,就可以随心所欲地进行各种操作了。那我们来点击它并看一下点击后的效果,如图1-117所示。 图1-117   如图1-117所示,这个Enable Editing按钮功能重新给我们带来了“光明”!   当“管理”好对象以后,就可以将战果保存下来了,这个就简单了,在File菜单下New、Open、Save、Save As等一些功能。   Step 2,依次选择File→Save(也可以使用快捷键Ctrl+S)。   至此,对象库管理的一个大致流程就完成了,当下次再打开link.tsr文件后就会发现对象库中的对象已经被优化过了(新增、修改、删除、合并等)。当然,你不想去改变link.tsr文件的话,操作Save As保存就行了,将当前对象库以其他命名保存。所以,对象库文件除了可以在Object Repository中通过导出生成,在这里也同样可以生成,只是存在着一些区别,一个叫Export、另一个叫Save As,仅此而已。 1.5.6.2  对象库的对比与合并   既然是在介绍Object Repository Manager,那就不得不提起它的两个“王牌级”的辅助工具:一个“Object Repository Comparison Tool”,另一个“Object Repository Merge Tool”。这两张王牌是内嵌在Object Repository Manager中的,使用它们的方式也很简单,分别是选择Tools→Object Repository Comparison Tool,以及选择Tools→Object Repository Merge Tool,不过千万要记住这里的“Tools”是对象库管理工具里面的那个。另外,这两个辅助工具和对象库管理工具一样,打开以后都是以独立的窗口显示的,和QTP程序窗口是分开的,好象它们都是一个个单独的小程序,让我们来看一下全部打开以后的效果,如图1-118所示。 图1-118   那么接下来,将使用两个实例来介绍这两个辅助工具的用法与作用,以及操作它们的一个大致流程。先来介绍Object Repository Comparison Tool,一起来看下面这个实例。   Object Repository Manager的实例2。   Step 1,依次选择Tools→Object Repository Comparison Tool。   打开对象库对比工具,进入后的界面如图1-119所示。 图1-119   如图1-119所示,打开这个工具以后会自动弹出一个New Comparison对话框,然后继续进行下一步操作。   Step 2,选择First file和Second file(所选文件如图1-120所示),然后点击OK按钮。 图1-120   再将两个文件分别选择完毕并点击OK以后,会进入图1-121所示的界面。 图1-121   如图1-121中所示,点击OK按钮后立即弹出了一个Statistics对话框,接下来就要进入下一步了。 Step 3,分析统计数字以及查看两个被比较的对象库之间的差异。   首先,需要分析统计出来的数据,Statistics对话框里所列的数据是相当直观的,如图1-122所示。 图1-122   通过这些统计数据可以让我们能更快捷有效地查看对象库之间的差异,图1-123显示的就是本实例中出现的两个对象库文件之间的差异。 图1-123   如图1-123所示,图形化的差异标识显得非常地清晰和直观,并且情况完全和统计对话框中的数据吻合。由此可见,Object Repository Comparison Tool的确是一个非常实用的辅助工具。   在自动化测试项目中,对象库管理人员在做对象库维护时,如果能善加利用这些小工具就一定能事半功倍,当然这只是第一步,目前只是停留在一个分析与确定的阶段,还需要一个步骤,完成对象库的维护工作才算最终完成。这个后续的步骤是什么?那就是对象库合并。读者又要问了,如何才能把不同对象库中的对象合并到同一个对象库中呢?接下来,作者要推介给读者的就是一个对象库合并工具Object Repository Merge Tool,继续看第二个实例,在这个实例中作者同样会做一个流程的操作。   Object Repository Manager的实例3。   Step 1,依次选择Tools→Object Repository Merge Tool。   打开对象库合并工具,进入后的界面如图1-124所示。 图1-124   同样,打开这个合并工具后也会自动弹出一个对话框,只不过它换了个名字叫New Merge而已,然后继续进行下一步操作。 Step 2,选择Primary file和Secondary File,然后点击OK按钮,如图1-125所示。 图1-125   如图1-125所示,我们仍然使用之前的两个对象库文件来完成本次实例,再将两个文件分别选择完毕并点击OK以后会进入图1-126所示的界面。 图1-126   如图1-126所示,点击OK后同样也是弹出一个Statistics对话框,里面包含了关键数据让我们可以一目了然地去掌握一些信息,然后在整个界面的左侧会显示初步合并后的结果(初步合并是自动且默认的),让我们来看一下(如图1-127所示)。 图1-127   如图1-127所示,在对象的左侧出现了一些数字而有的对象却没有,这里的“1”代表第一个对象库里的对象,而“2”则代表第二个对象库里的对象,那些没有数字的则说明它们是两个对象库共有的对象(共有的对象是要所有属性完全一致才算)。那么接下来再来看一下整个界面的右侧部分,如图1-128所示。 图1-128   界面我们已经看到了,图例中也做了相应的说明,需要注意的是,当前两个对象库中的对象没有冲突,所以合并的工作就由系统代劳了,不用再手工去设置,也就是因为这个原因,下面的Previous Conflict和Next Conflict两个按钮灰色。由于没有冲突,所以,到这一步对象库合并工作就算结束了,左侧的初步显示的合并结果也将成为最后的结果,最后只需要记得将Statistics对话框关闭掉,并把“战果”保存下来即可。  以上是没有冲突的一个情况,接下来继续再看如果两个对象库之间存在冲突,我们的Merge工具如何来应对,重复的过程就跳过了,直接来看冲突结果,如图1-129所示。 图1-129   从图1-129中可以看到,Merge Tool会准确标记出那些需要合并但是又有冲突的对象的位置。那么此时此刻,需要继续做些什么呢?那就是给出一个解决方案,比如在两个冲突的对象间到底保留哪个对象等。先看一张图例,如图1-130所示。 图1-130   如图1-130所示,Resolution Options区域就是Merge Tool的“杀手锏”,上半部分会告知用户一些所需的信息,而下半部分则提供了3种不同的解决方案(见图1-130)。默认选择最下面的Keep both objects,也就是因为这个选项才会有前面那一张初步合并的结果图,保留了第二个对象库中的“百度一下,你就知道”Browser对象,但又为了使其不冲突,所以自动更名为“百度一下,你就知道_1”。至于其他两种方式就不多做介绍了,意思已经相当明确了,读者可以自行将各种解决方案都体验一把。最后,只需要和前面一样,将“战果”保存下来就行。   到此,整个Object Repository Manager小节就介绍完了,它当之无愧的是对象库的最高“指挥官”。同时,作者再次重申:管理对象库就好比管理你的程序,是一项重点工作,必须有统一的命名规则和标准等。在做项目时,如果能有一个优秀的公共对象库供自动化测试工程师调用,那么工作效率势必会翻倍! 1.5.7  总结   本章节基本上是围绕QTP工具本身的功能在做介绍,同时作者也分享了不少额外补充内容,比如说想要做好自动化测试项目,那么管理好对象库则是非常关键的一个环节等一些思想及实际经验。所以,读者不仅仅可以在本章节中学会如何去使用对象库相关的功能,更重要的是读者可以逐步学会,如何将一些好的思想经验和实际的功能操作去相结合。需要注意的是,本章节中介绍的功能都是对象库中最最重要的那些,所以读者务必要掌握操作技巧并能熟练运用。   知识点巩固和举一反三练习   一、请在本地对象库中任意添加一些对象并将该对象库导出。   要求1:规范命名添加后的对象,养成好习惯。   要求2:对象添加完毕后导出至D盘目录下并命名为local.tsr。   二、将“练习一”中已完成的local.tsr对象库转变成可供多人调用的公共对象库。   要求1:在对象库管理工具中打开local.tsr文件并任意新增一些对象(即维护对象库)。   要求2:维护完毕后以另存为的方式将维护后的对象库保存至D盘目录下并命名为public.tsr,使其成为一个公共对象库。   要求3:调用公共对象库。 1.6  对象库(下)之进阶编程篇   阶段要点   ● 掌握手写代码的3种方式。   ● 明确测试对象(TO)与运行时对象(RO)的区别。   ● 4种操作对象封装属性的方法。   ● 你也可以是一名魔术师、黑客。   ● 几种常见的QTP无法识别或识别错误的原因。 1.6.1  引言   在对象库上篇这个章节中,相信读者已经对对象库的使用以及其他各个方面有了一个全面的了解。那么从现在开始,作者将引领读者开始基于对象库编程的学习,从而掌握对象库编程的知识与技巧。读者如果觉得在对象库上篇中还有不能够完全掌握的地方,建议重新学习直到完全掌握为止,因为只有打好了上篇所介绍的基础,才能学习好下篇的知识,真正做到融会贯通。   在前面的章节“1.4  认真并请远离QTP的脚本录制模式”中,作者也已经提到过在QTP自动化测试中绝对不只是单纯的录制与回放,因为只要稍微复杂点的业务,录制模式就不能满足我们的需求了。那么,要想完成自动化测试业务流,就必须依仗QTP的编程模式,举个例子,就比如多个脚本间的数据中转,如果没掌握QTP的编程知识,那一定就没有办法实现。在1.4章节中,作者已经展示过了QTP的代码片段,但并没有深入去介绍到底如何去手写代码,因为这些内容都是本章节的重点。接下来就要开始逐步地详细介绍QTP的第一种编程模式“对象库编程”了,这种编程模式同样也是作者最推崇的一种模式,因为它是QTP的一个特色和亮点。本章节也可以说是到目前为止最难学习的一个章节,包含的内容也很多,包括对象库的编程技巧、编程知识、编程原理等。不过,作者相信在学习完本章节以后,读者的QTP水平一定会有一个明显的飞跃。当然,条件是读者必须深入掌握本章节的所有内容,并能够举一反三。如果对象库编程没有学好,那将会成为读者今后应用QTP自动化测试技术的绊脚石。 1.6.2  基于Expert View的对象库编程必备知识 1.6.2.1  手写代码的3种方式   在QTP的对象库编程中,存在着3种编写代码的方式,依次分别是:步骤生成器、Complete Word、对象拖动生成。接下来,作者就依次介绍这些手工生成代码的方式。   第一种:步骤生成器方式。   先要介绍的是步骤生成器方式,这种方式可以说是QTP的一个“元老”,在QTP一出现就具备了。严格来说,它应该算是QTP的一种功能,接下来就让我们一起动手完成一系列的实例,从而掌握这种编程方式。   既然是一种功能,那么就首先来看一下它所处的地理位置,以及如何去访问到这个功能。   依次选择QTP上方菜单栏→Insert→Step Generator。   操作快捷键F7。   现在随意使用一种手段去使用它,并来认识它,如图1-131所示。 图1-131   如图1-131所示,可以看到这就是步骤生成器的一个界面。在Category中有3个下拉选项(在图中已经展示出来了),这3个选项就分别代表了步骤生成器可以生成的3种不同类别的代码。 小提示:在QTP的Expert View编程中,一行代码就是一个步骤,所以,生成了一行代码也就等于生成了一个步骤。   接下来就逐一生成不同类别的步骤,QTP的“F7”一共可以生成3个类别的代码,所以在这里就划分成3个实例进行操作演示并同步讲解。   实例1:Test Objects。   预备工作:添加一些对象进对象库,事先已经添加好了,如图1-132所示。 图1-132   如图1-132所示,我们看到对象库里存在一些对象了,那么,接下来需要完成这样一个任务,就是生成一句点击“百度一下”按钮的代码。   Step 1,选中Test Objects下拉选项,选择完毕后,如图1-133所示。 图1-133   如图1-133所示,我们可以看到,在选中Test Objects这个分类以后,它下面的那个Object下拉框默认显示了一个对象,这个对象就是对象库中的那个父对象。然后再来看Object下拉框的下面,它是一个Operation下拉框,顾名思义就是,这个下拉框可以选择各种不同的操作,不过需要注意的是,Operation下拉框中的可选操作都是基于当前所选择的对象,当前所选对象不支持的操作是选择不到的。我们可以看到,当前默认选择的是Sync操作,这个不用去管它,是系统默认的,不用去深究。接着在下面有一个Arguments的区域,在这里会显示所选操作的一些参数,当前在图1-133中这块区域没有任何东西,是因为Sync这个操作方法没有参数可设置。再接着下面是一个Return value的勾选框,如果当前的对象操作可以有返回值的话,就可以勾选了,并且还能在后面的文本框中输入返回值的名称。最下面就是一个步骤生成预览的区域,这个就不多解释了。如果生成一个步骤以后,还要继续生成其他后续步骤,则勾选上Insert another step,这样在点击了OK按钮以后,“F7”窗口就不会关闭了,可以继续生成下一个步骤代码。好了,也借此机会把这个界面上的一些功能大致介绍了一下,让读者有个了解,在接下来的操作中就不再重复介绍了。 那么继续回到主题,显然现在默认所选的对象不是我们需要的,需要的是生成一句对“百度一下”进行操作的代码,那么此时该怎么办呢?让我们来看下一步。   Step 2,点击Object下拉框右边的“方块+箭头”按钮(在图1-133中),在点击以后会出现一个Select Object for Step的界面,如图1-134所示。 图1-134   如图1-134所示,可以看到在Select Object for Step这个界面中可以进行对象的选择,除此以外,还包括了一些其他的功能,比如对象查找、从程序指定对象等,这些功能不是重点,就不多做介绍了。那么,我们就选择“百度一下”对象,然后点击OK按钮,点击后如图1-135所示。 图1-135   如图1-135所示,现在可以看到Object下拉框中已经变成了“百度一下”这个Button对象,并且Operation下拉框也随之变成了Button对象下的一些操作方法,默认是Click(恰好是需要的操作方法)。 Step 3,进行后续设置。这一步就很简单了,我们就拿“百度一下”这个Button控件为例,可以从图1-135中看到Button控件的Click方法有3个参数,其中参数x和参数y是可以设一个值的(如果需要的话),参数BUTTON由于不能设置具体的值,所以QTP显示了。那么接下来就试着设置一下参数x和参数y吧,在设置之前大家请注意步骤生成预览区域的代码,目前是:“Browser("百度一下,你就知道").Page("百度一下,你就知道"). WebButton("百度一下").Click”,等设置了参数以后再来看看步骤生成代码区域。   Step 4,设置x?=?97、y?=?120。分别点击x与y的Value对应列,点击后的效果如图1-136所示。 图1-136   从图1-136中可以看到,x的值已经可以设置了,不过要注意的是,准备输入的值需要与Type类型相关,Integer类型的输入值当然不可以是字符串类型,反之亦然。在继续输入Value之前作者还要介绍一个小功能点,它就是<#>按钮(图1-136中0的右边),这个按钮的作用是参数化,可以在这里将Value参数化,然后在QTP的Data Table里设置各种需要的测试数据。来看一下它的界面,首先点击一下<#>按钮,点击后的效果如图1-137所示。 图1-137   如图1-137所示,选择Parameter后就可以将参数进行参数化设置了,并且可以选择参数化数据的位置(Global sheet或Current action sheet)。另外,Parameter这个下拉框的其他一些选项在这里就不多做介绍了。读者目前只需要知道步骤生成器里也可以将数据参数化即可,关于QTP的Data Table会在后面的一个章节独立介绍。   回到正题,待x和y都设置好以后,再来看一下步骤生成预览区域的代码,看看是否起了变化,如图1-138所示。 图1-138   如图1-138所示,在Click方法后面多出了两个参数,就是我们设置的x和y,当然,对于一个Button控件来说,这些参数其实可有可无,除非是一些很特殊的情况,一般情况下都是不用去设置的,作者在这里只是做个演示。   第一种手写代码的方式就写到这里,这个方式在QTP 8.x的时候还是经常被使用到的,接下来继续看一下其他代码生成方式。 第二种:Complete Word方式。   相信做过开发或者写过Java代码的读者都应该对Complete Word这个名词不陌生?它就是开发脚本过程中的催化剂。有了它就可以不用去死记硬背一些代码;有了它就不会再因为代码不小心编译错误而烦恼,比如最常见的拼写错误;有了它我们的编程速度会明显提升。因此,Complete Word不但对开发程序相当重要,对测试脚本的设计也同样起着至关紧要的作用!下面就来介绍如何在QTP中实现Complete Word。   首先打开QTP并依次点击上方菜单栏下的Edit→Advanced,在找到Complete Word后,读者会发现一个很尴尬的事情,就是QTP设定的Complete Word正是平常一直使用的切换中、英文的快捷键Ctrl+Space,而由于快捷键Ctrl+Space被优先认定为中、英文切换组合键,所以会导致QTP的Complete Word无效。   解决步骤。   (1)右键单击语言栏,点击设置。   (2)点击键设置,选择输入法/非输入法切换。   (3)点击更改按键顺序。   (4)如图1-139所示的选择,然后点击确定。 图1-139   解决这个问题是很简单的,只不过如果读者平时习惯使用Ctrl+Space来中、英文切换,那为了使用QTP只能不用Ctrl+Space切换中、英文了。   在完成了以上4个步骤之后,就可以在Expert View中使用快捷键Ctrl+Space来调出Complete Word了,如图1-140所示。 图1-140   接下来,来做一个实例,假设当前的Expert View中有以下两个函数: Function test_hello_world_one      msgbox "test1"   End Function Function test_hello_world_two      msgbox "test2"   End Function   现在,需要调用这两个函数,那么就可以直接使用Complete Word来效率地完成了,如图1-141所示。 图1-141 是不是非常方便呢?再举一个例子,假设现在要使用Complete Word生成下面这段代码: Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("wd").Set "QTP自动化测试技术领航"   在这里,作者形象地写出生成这句代码的整个过程,使得读者可以掌握快速写代码的技巧,其实非常简单,读者看了以后就能学会,过程如下所示。   Step 1,敲击键盘Ctrl+Space,然后再敲击键盘上的字母B,此时,QTP会自动地找到以B开头的所有Complete Word(见图1-142),接着敲击回车,此时Browser这个Word就会输入到Expert View里,然后再敲击“左括号”,完成目前所有步骤后的效果见图1-143所示。 图1-142 图1-143   小提示:QTP中所有的代码编译都是基于英文输入法的,如果是中文输入法,检查语法的时候会显示错误。如果Step1中输入的是中文输入法下的左括号,那Browser的具体对象是不会跳出来的。   Step 2,从图1-143中可以看到,在敲击了左括号以后,QTP会自动将Browser控件的具体对象补全,(补充:这里QTP所补的具体对象一定要是对象库中存在的,换句话说,只有对象库编程才支持这种方法,另外,如果当前对象库中假设有两个Browser或其他任意对象(如Page、WebButton、WebEdit等所有),那么QTP会给出一个选择框来选择当前需要的对象,见图1-144),然后输入右括号来完成对象的描述,在这里描述的是Browser对象。 图1-144   Step 3,完成了Browser对象后,现在需要调用它的子对象Page对象,此时只要敲击键盘上的“点”以后,就会弹出一个选择框,里面包含了Browser对象的所有方法、属性和子对象,然后敲击字母P使其直接定位到以P开头的Word,并最终定格在Page这个子对象的位置上,如图1-145所示。 图1-145   接着就和Step2中一样了,使用左右括号来完成Page对象的描述。再以同样的方法完成WebEdit对象的编译后,就彻底完成了对象定位,如图1-146所示。 图1-146 Step 4,完成最终对象定位以后,还没有真正的结束,最后一步就是给予最终定位到的对象一个“操作动作”,也就是常说的方法。在此,仍然需要敲击“点”来调用WebEdit对象下的各种方法或属性,在这里,选择的是实例要求中的Set方法,待所需的方法选择完成,最后一步是设置这个方法的参数值,这样,整个流程就完成了,如图1-147所示。 图1-147   总结:Complete Word是目前QTP编写代码过程中使用率最高的一种方式,一旦操作熟悉以后,可以大大提高QTP自动化测试的工作效率。   第三种:对象拖动生成方式。   最后,作者要介绍的一种方式是把对象拖动到Expert View里来快速生成代码,这种方式只支持QTP10.0以上的版本,在之前的早期版本中没有此项功能,这项功能的出现也抢占了一部分使用第二种方式的市场份额。先来看一下这个新功能所在的界面,如图1-148所示。 图1-148   如图1-148所示,可以看到所有的测试对象都出现在一个叫Available Keywords的窗口中,拖动对象生成代码必须也只能在这个窗口进行,其他位置(比如对象库)里就不支持对象拖动。在这个窗口中不光显示所有的测试对象,还会显示所有的Functions,包括外部调用的和脚本内部本身的Function,同时也可以看到右键单击某个对象后可以点击Open Resource,可以通过这样一种方式来进入对象库,并直接定位到所选的对象在对象库中的位置,可以说这个窗口是非常实用的一个功能。   那么接下来就介绍拖动生成代码的方法和一些必知的知识。拖动操作其实是非常方便的,只需要在窗口中定位到所需要的控件,然后Drag&Drop到Expert View中即可,如图1-149所示。 图1-149   如图1-149所示,一句代码就自动生成了,生成最终对象WebEdit(“wd”)的同时也默认给出了一个最基本的方法Set,方法后的参数则需要自己填写,并且也可以看到,在生成完毕后,光标自动进到了第二行。在这里,读者需要知道,我们不仅仅可以定位最终对象,也可以对一些父对象进行拖动并生成代码,现在拖动Page(“百度一下,你就知道”)这个对象来试试看,如图1-150所示。 图1-150   如图1-150所示,只用一句代码就生成了,这次生成的是Page连同其父对象,并默认给出了一个Sync方法(不用关心为什么默认会生成这个方法),如果需要使用其他方法,把.Sync删除,重新“点”一下即可。   总结:   随着代码拖动生成这个功能的实现,开发QTP脚本更加快捷了,当然这里有一个小小的不足就是会默认生成一个方法,这个方法往往不是想要的,只能手工去删除然后重新“点”出新方法,这样会浪费一点时间,但总体来说,代码拖动生成是目前为止最高效的一种方法,基本上使用QTP10.0以上版本的自动化测试工程师都会选择这种方式,也是作者强烈推荐的!当然,这个功能只能支持基于对象库的编程。 1.6.2.2  其他补充知识点   在上一个小节里,已经介绍了快速编译QTP脚本代码的3种方式,在本小节中,作者将补充一些QTP编码过程中的小技巧。   1.QTP IDE中的垂直分割选取   相信读者应该都知道在大多数开发的IDE中都会有垂直分割选取,它可以在文本中选取一个特定的垂直柱,并可以进行多行同时输入代码,如图1-151所示。 图1-151   当然,QTP的IDE也不例外,同样也可以使用此功能,可以省去很多不必要的操作,让我们来做一个实例。   Step 1,新建一个Test,输入如图1-152所示的代码。 图1-152   Step 2,现在可以使用垂直选取的方法轻松地对多行代码进行同时更改,首先,要选取到“垂直柱”。选取方法:在左上角点击鼠标左键往右拉一定距离不放手,与此同时,再点击一下鼠标右键,然后,就可以轻松地进行垂直段的选择了,如图1-153所示。 图1-153 Step 3,选取垂直柱之后,可以输入想要更改的代码,比如这里输入“dim”,QTP就会同时对多行进行输入并覆盖原先的Public,如图1-154所示。 图1-154   如图1-154所示,已经批量修改完毕了,同时这个小小的功能也就介绍完了,大家可别小看它,有时候往往能够帮助我们节省很多时间,提升工作效率。   2.代码换行符   假设现在有这样一句代码:Browser("51Testing软件测试网-中国软件测试人的精神家园").Page("51Testing软件测试网-中国软件测试人的精神家园").Image("51Testing软件测试网").Click,这句代码非常长,当前的Expert View无法完全显示这行代码,只能看到一半,这样的话看代码就会显得相当不方便,需要拖动滚动轴才能看到后面的代码。   如何解决?QTP提供了一个代码换行符号,它就是“_”符号。有了它,就可以将一句代码切分成好多行,缩短代码的长度,可以清晰地浏览代码,可以这样切分,比如: '#1 Browser("51Testing软件测试网-中国软件测试人的精神家园")._ Page("51Testing软件测试网-中国软件测试人的精神家园")._ Image("51Testing软件测试网").Click '#2 Browser("51Testing软件测试网-中国软件测试人的精神家园")._ Page("51Testing软件测试网-中国软件测试人的精神家园").Image("51Testing软件测试网").Click '#3 Browser("51Testing软件测试网-中国软件测试人的精神家园")._ Page("51Testing软件测试网-中国软件测试人的精神家园")._ Image("51Testing软件测试网")._ Click   但是,我们不能这样切分,比如: '#1 – “点”放在换行符号后面 Browser("51Testing软件测试网-中国软件测试人的精神家园")_. Page("51Testing软件测试网-中国软件测试人的精神家园")_. Image("51Testing软件测试网").Click '#2 – 换行符号的插入导致描述一个对象(Image)的代码被分割成2段 Browser("51Testing软件测试网-中国软件测试人的精神家园")._ Page("51Testing软件测试网-中国软件测试人的精神家园").Image("51Testing _软件测试网").Click '#3 – 在结束时不能使用换行符号 Browser("51Testing软件测试网-中国软件测试人的精神家园")._ Page("51Testing软件测试网-中国软件测试人的精神家园")._ Image("51Testing软件测试网").Click_   换行符号对于代码的可读性等方面起着重要的作用。其实,细心的读者应该已经在前面的章节中看到过这个“_”符号了,它将会是今后经常用到的工具 3.Alt+G快捷键   位置:依次选择QTP上方菜单栏→Edit→Advanced→Go to Function Definition。   这是一个比较有用的快捷键,假设目前的脚本有1000行代码,代码中有很多Function,此时如果有了这个快捷键,就能够将光标定位到调用Function的这行代码,然后按下Alt+G,快速跳转定位到该调用的Function在脚本中的位置,使得能够相当快速地查看到Function里的功能,演示如下: '假设是第20行代码 Call func_test01  '调用func_test01函数 – 光标定格在本行,PRESS "Alt+G" … … … '假设这个函数从第850行开始 Function func_test01    'Alt+G后,光标会直接停留在此行,瞬间从第20行跳到850行   …  …   Msgbox "test01"   …  … End Function   Alt+G功能介绍完了,读者需注意的是,它并不适用于外部调用的Function(函数)。   4.Ctrl+Shift+Space快捷键   位置:依次选择QTP上方菜单栏→Edit→Advanced→Argument Info。   在方法后往往需要设置一些参数,但是那么多参数难道都去背下来吗?显然不用,QTP会自动提示该方法的参数,如图1-155所示。   但是有时候会因为某些操作导致参数提示消失了,此时就可以使用Ctrl+Shift+Space快捷键来重新显示参数提示,这个小功能相当实用。   5.基于类和函数的Complete Word   在上一小节中,介绍了QTP可以使用Ctrl + Space快捷键来激活Complete Word的功能,增强脚本的编写效率,此方法只能对于一些vbs函数进行智能提示,对于类是不支持的。那么在这里,作者介绍QTP的一个支持类的Complete Word。方法很简单:Alt + .(Alt键加“点”键的组合)。   首先,打开QTP,输入以下代码: Class libClass      Function libFunction         Msgbox "libfunction"      End function     End Class   然后,使用Alt+.快捷键进行激活,如图1-156所示。 图1-156   注:此方法只适用于QTP 9.2及其之前的版本。  6.学会使用With…End With   首先来一起看以下这段脚本: '51Testing首页 Browser("51Testing软件测试网").Page("51Testing软件测试网")._ WebButton("WebButton").Click Browser("51Testing软件测试网").Page("51Testing软件测试网")._ WebEdit("password").Set "Yu Jie" Browser("51Testing软件测试网").Page("51Testing软件测试网")._ WebEdit("username").Set "Jerome Yu" '百度首页 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("wd").Set "QTP自动化测试技术领航"   在上面这段脚本中使用了很多代码换行符,这个在前面已经介绍过了。不知道读者看了上面这段脚本以后有没有发现一个问题,那就是每句代码中前面半段都是相同的,比如现在要操作51Testing首页中的一些对象,不难发现不同操作对象前的祖父对象和父对象都是一致的,遇到这种情况,就完全可以使用With来解决,见下面这个脚本: '51Testing首页 With Browser("51Testing软件测试网").Page("51Testing软件测试网") '注意后面没有点      .WebButton("WebButton").Click  '请注意“点”的位置,句首需要有一个“点”      .WebEdit("password").Set "Yu Jie"      .WebEdit("username").Set "Jerome Yu" End With   With就是用来提炼公共部分的,现在再去看脚本,是否一下子感觉清晰、有序了很多?又比如同一个脚本中需要操作两个网站,此时用With效果也非常好,见下面这个脚本: '51Testing首页 With Browser("51Testing软件测试网").Page("51Testing软件测试网")      .WebButton("WebButton").Click      .WebEdit("password").Set "Yu Jie"      .WebEdit("username").Set "Jerome Yu" End With '百度首页 With Browser("百度一下,你就知道").Page("百度一下,你就知道")      .WebButton("百度一下").Click      .WebEdit("wd").Set "QTP自动化测试技术领航" End With   如此一来,就可以很直观地读代码了,一目了然。With可以大大提高代码可读性,这同时意味着方便了我们以后的维护工作。   另外,可以随意“With”公共部分,也可以嵌套With,见如下脚本: '百度首页嵌套在51Testing首页中 With Browser("51Testing软件测试网").Page("51Testing软件测试网")      .WebButton("WebButton").Click      .WebEdit("password").Set "Yu Jie"      .WebEdit("username").Set "Jerome Yu"      With Browser("百度一下,你就知道").Page("百度一下,你就知道")           .WebButton("百度一下").Click           .WebEdit("wd").Set "QTP自动化测试技术领航"      End With End With '百度首页 -- 提炼其他公共部分 With Browser("百度一下,你就知道")      .Page("百度一下,你就知道").WebButton("百度一下").Click      .Page("百度一下,你就知道").WebEdit("wd").Set "QTP自动化测试技术领航" End With   因为好用,所以With…End With是日常编程过程中经常使用的工具。   总结:   虽然本小节介绍的都是一些很小的应用技巧,但是往往能够给我们带来工作效率,QTP中的技巧也远远不止这些,相信更多的实用性技巧在于读者平时的挖掘与积累,作者认为,适合自己的才是最好的! 1.6.3  封装对象模型—Test Objects VS Run-time Objects 1.6.3.1  解密测试对象与运行时对象   在QTP里的封装对象共分两个概念,一个是Test Objects(俗称TO)“测试对象”,另一个是Runtime Objects(俗称RO)“运行时对象”。可能大部分读者已经不记得了,在上篇介绍Spy的那个章节中,作者在末尾处已经初步提到过TO与RO,我们来回忆一遍吧,以下是原文:“最后作者要说下,也算是一个预告吧,后者也就是Identification Properties以后会牵涉到对象库编程中的两个重大概念Test Objects(测试对象,俗称TO)和Run-time Objects(运行时对象,俗称RO),在为TO或RO进行编程的时候使用的就是QTP自己封装的这些对象属性。关于前者Native Properties也有其独特的调用方法。”   原文中提到了Spy中的Native Properties以及Identification Properties,后者就是要在本小节重点讲的封装对象的属性,而前者会在后面介绍对象自身接口的属性时介绍。不过无论是哪一个,都和对象库编程有着紧密的联系,基于对象库的编程就是基于这些属性来完成的。   继续回到主题,TO和RO这两个概念从字面上来看并不是很好理解,也容易混淆。作者现在就用最简单的描述来介绍测试对象(TO)与运行时对象(RO)的区别:TO就是被添加到对象库中的对象,RO其实就是被测试软件在运行时实际所运行的那个对象。但是,读者要记住,无论是TO还是RO,它们都属于QTP封装的对象,共同使用QTP封装好的一些属性,RO就有点特殊了,它也可以调用自身接口的属性,这个是在下个小节中要讲述的内容,读者在这里只要知道RO也调用QTP封装好的属性就可以了。   在上篇中已经讲解过QTP鉴别对象模型的机制Object Identification,QTP识别对象通常就是先在对象库里添加测试对象,这些对象全部都存有一些特征属性的值,然后在被测软件运行的时候,QTP会根据脚本里的对象名字,在对象库里找到相对应的测试对象,并根据这些对象的特征属性描述,在被测试软件里搜索并找到相匹配的实际正在运行的对象,最后就可以对这些实际运行的测试对象进行操作了。如果在这个过程中没有找到任何相匹配的对象,那么QTP就会报个“找不到该对象”的错误。最后,作者用最简单的一句话总结一下测试对象(TO)与运行时对象(RO)两者之间的紧密关系:测试对象是为了识别运行时对象而存在的。 1.6.3.2  三兄弟GETRO、GETTO、SETTO各显神通@@   在理解了Test Objects与Run-time Objects以后,就要开始学习如何获取到对象的属性了,毕竟自动化测试分成两大块,第一个就是自动操控被测对象。第二个就是获取各种所需要的对象属性,获取属性的作用也可以分成两部分,一个最重要的作用也是最常用的就是验证,验证离不开这些属性,只有这些属性才能证明软件的操作是正确的还是错误的,另一个用途就是获取到一些有利用价值的属性来做后续的事情。还有一个分支,就是给对象库里的对象通过代码自动设置一些属性,以达到特殊的测试目的和要求。所以,在自动化测试过程中,对于“属性”的掌控是很重要的,接下来就详细介绍操控属性的各种方法,待掌握以后再举一些项目中基本的实际使用例子来帮助读者理解各种方法的作用,并证明其实属性的获取是自动化测试过程的桥梁。所以,这些对属性操作的方法绝对是重点中的重点内容。那么就让我们看看对象属性的处理方法有哪些,并逐一进行介绍。   素材:www.baidu.com百度首页,所需添加对象如图1-157所示。 图1-157   1.GetTOProperty()   ● 基本含义:获取对象库中某个对象的某个属性的值。   ● 公式:ReturnValue =对象.GetTOProperty("封装属性名")   ● 基础示例: ValueOfTo = Browser("百度一下,你就知道").Page("百度一下,你就知道").Link("新闻").GetTOProperty("text") MsgBox ValueOfTo ● 运行结果与分析。   获得Link对象在对象库中记录的属性“text”的值,并返回给变量ValueOfTo,最后以对话框形式显示这个值,显示后的结果如图1-158所示。 图1-158   从图1-158中可以看到,已经成功获取到了该对象的“text”属性值并弹出对话框显示。在这里,读者需要注意,如果在实际运行的过程中,“text”属性已经发生了改变,假设变成了“贴吧”,在这里仍然是获取到“新闻”这个值,因为GetToProperty这个方法就是获取对象库中的对象的属性值。   企业项目案例臆测。   如果项目中碰到这样一个需求:界面上有一个test1的下拉框控件,每次在刷新页面后都会选中默认值A,现在业务中需要选择其他值,我们需要用QTP做比较。像碰到这种案例时,就可以通过使用GetToProperty和GetRoProperty(后面会讲到)这两个方法协助完成,只要在下拉框选中默认值的时候将其加入对象库,这样对象库中的记录值就永远会是这个默认值了,然后在实际运行时选取其他值,并获取此时这个对象的实际选取的值,最后对两个值做一个比较,通过这个思路就能解决这个需求了。   2.GetTOProperties()   ● 基本含义:获取对象库中某个对象的所有属性的值。   ● 公式:ReturnValue =对象.GetTOProperties()。   ● 基础示例: Set TestObject  = Browser("百度一下,你就知道").Page("百度一下,你就知道").Image("百度首页logo")  '1 Set Properties = TestObject.GetTOProperties()  '2 PropertiesCount = Properties.Count  '3 Print "对象总计存在" & PropertiesCount & "个封装属性。"   '4 For i = 0 To PropertiesCount – 1  '5    PropName = Properties(i).Name    PropValue = Properties(i).Value    Print  PropName & " = " & PropValue Next Set Properties = Nothing  '6 Set TestObject = Nothing  '7   ● 运行结果与分析。   首先,GetTOProperties这个方法获取的是一个数组,这个务必要清楚。因为是数组,所以想要一个个打印属性值的前提条件就是,先要得到对象的属性个数,然后再通过循环的方法把一个个属性和属性值打印出来。但是,GetTOProperties并没有提供Count方法,所以,只能通过其他方式去获得对象属性的Count(见代码片中的1~3)。在获取到Count以后,就能够一个个将值读出来了(见3~5),当然,最后也别忘了释放对象(见6~7)。   为了更好地显示结果,所以才在这里使用了Print函数(QTP自带的函数,调用后会将一些信息填写到Print Log窗口中),最后就让我们一起来看一下打印结果,如图1-159所示。 图1-159   ● 企业项目案例臆测。   一般情况下,在项目中很少会使用到GetTOProperties这个方法,因为通常不会碰到需要使用到这个方法的需求。所以,GetTOProperties这个方法也是所有操作对象属性中最少使用的一个方法,也正是因为如此,作者才没将本小节的标题写成“四兄弟”。 3.SetTOProperty()   ● 基本含义:设置对象库中某个对象的某个属性的值。   ● 公式:对象.SetTOProperty“封装属性名”,“封装属性值”。   ● 基础示例: Browser("百度一下,你就知道").Page("百度一下,你就知道").WebButton("百度一下").SetTOProperty "name","百度一百万下" MsgBox "时间停止,大家一起欣赏此时对象库中WebButton"百度一下"的name值!"   ● 运行结果与分析。   SetTOProperty方法的作用就是改变对象库中的值,当然,使用代码形式的修改对象属性属于临时性的,只在脚本运行时有效,一旦脚本运行结束,对象库里的属性值就会还原,它的生命周期是短的,不过已经足够我们做很多事了。   在本段代码片中,要修改的是WebButton(“百度一下”)这个控件在对象库中的属性,为了证明修改最终是成功的,那么让我们一起先来看一下修改以前的name属性值,如图1-160所示。 图1-160   从图1-160中可以看到,name的属性值是“百度一下”,然后作者把它改成了“百度一百万下”,我们前面讲过,由于SetTOProperty这个方法的生命周期很短,所以,如果要看到修改的效果,必须在脚本结束前,将脚本停住,所以,作者在脚本里加了一个MsgBox方法,并将本行设置断点,这样QTP就会停止下来,此时去刷新一下对象库就能看到百度一下这个按钮的name值已经改变了,如图1-161所示。 图1-161   如图1-161所示,此时对象库中“百度一下”这个控件的name属性值已经变成了作者所设置的“百度一百万下”,到此也证明了实验已成功。需要注意的是,使用SetTOProperty方法,后面的参数是不需要加括号的,因为它没有返回值,当然这是VB中的知识点,在这里稍许提一下,如果在这种情况下加括号是一定会报错的,切记!   小提示:在QTP中,断点的快捷键是F9,使当前断点不生效/生效的快捷键是Ctrl+F9,取消所有断点的快捷键是Ctrl+Shift+F9。   ● 企业项目案例臆测。   可以和GetToProperty那个案例结合,假设现在需要将对象库中的对象属性值转变成其他指定的值,这种情况下,就可以使用SetTOProperty方法了。另外,使用SetTOProperty这个方法还有其他好处,比如可以解决一些共享对象库的对象修改和管理问题等。   这里还有一个很经典的自动化测试案例告诉读者,假设某个窗口上有很多待检查的记录,每条记录右边都有一个Check按钮用来检查各条记录。但是,记录个数是不定的,所以Check按钮个数也就不定,只有一个Edit控件显示记录个数。要对每条记录进行检查,也就是要点击每个Check按钮。但是Check按钮由于个数不定,根本没法将每个Check对象都添加到对象库中,因为个数可能太多了(上百个),如果硬要一个个添加到对象库中,是很烦的事。在这里告诉大家一个好办法,就添加一个Check按钮对象,它设有两个特征属性,分别是:label=OK、index=0,然后用下面的脚本就可以完成该自动化任务了: buttonNum = CInt(JavaWindow("Test").JavaEdit("Record Num").GetROProperty("value")) For buttonIndex = 0 to buttonNum -1     JavaWindow("Text").JavaButton("Check").SetTOProperty("index",buttonIndex)     JavaWindow("Text").JavaButton("Check").Click Next 分析:这段脚本的第一行代码是先通过Edit控件来获取页面上Check按钮的个数,然后通过循环并利用SetTOProperty方法一个个地去改变对象库中那个添加好的Check按钮对象的index,这样只需要添加一个对象就可以实现N个相同对象的操作了。   或者还有这样一个经典案例,假设窗口上有New、Modify、Delete、Check等好几个按钮对象,需要把这几个按钮一一按过去,这种情况下也只需要在对象库里只添加一个按钮对象就可以了,假设它叫“AnyButton”,label的特征属性值随意填写即可,然后用下面的脚本来实现: JavaWindow("Test").JavaButton("AnyButton").SetTOProperty("label","New") JavaWindow("Test").JavaButton("AnyButton").Click JavaWindow("Test").JavaButton("AnyButton").SetTOProperty("label","Modify") JavaWindow("Test").JavaButton("AnyButton").Click JavaWindow("Test").JavaButton("AnyButton").SetTOProperty("label","Delete") JavaWindow("Test").JavaButton("AnyButton").Click JavaWindow("Test").JavaButton("AnyButton").SetTOProperty("label","Check") JavaWindow("Test").JavaButton("AnyButton").Click   4.GetROProperty()   ● 基本含义:获取实际在运行时的某个对象的某个属性的值(不是从对象库里面获取)。   ● 公式:ReturnValue =对象.GetROProperty("封装属性名")。   ● 基础示例: ValueOfRo = Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("搜索内容编辑框"). GetROProperty("value") MsgBox ValueOfRo   运行结果与分析。   首先,GetROProperty方法访问的是实际正在运行的对象的封装接口,在这里做一个实验来证明这个方法的效果。第一步就是添加WebEdit这个“搜索内容编辑框”控件到对象库中(注意,在还没有输入过任何字符的时候进行添加),添加完以后可以看到对象库中该对象的value属性的值是空的,如图1-162所示。 图1-162   如图1-162所示,value的值是空值,因为还没有在编辑框中输入任何值的时候添加的。接着做第二步,在编辑框中输入“QTP自动化测试技术领航”,如图1-163所示。 图1-163   在完成这些操作后,执行下代码片中所写的脚本,其结果显示如图1-164所示。 图1-164 图1-164中显示的结果已经很明显了,证明了GetROProperty的方法是获取实际在运行时对象的属性值“QTP自动化测试技术领航”,而不是该对象在对象库中的值“空值”。细心的读者一定会发现,刚才在查看对象库中WebEdit控件value值的时候,是通过点击Add Properties按钮,然后在弹出的界面中看到的,作者在这里只是看了一下这个属性的值,并没有将这个属性添加到Description Properties窗口中,知道为什么不能添加到该窗口吗?因为一旦添加进去,QTP就会报错了。在之前的内容中讲过QTP识别对象的机制,就是通过Description Properties窗口去匹配的,如果把value属性加到这个窗口下,那么在输入“QTP自动化测试技术领航”这些字符以后,实际运行时QTP就会因为识别不了对象而导致报错,因为Description Properties窗口下所有的属性都是必须要匹配到的识别属性。   ● 业项目案例臆测。   GetROProperty方法在项目中使用率就太广泛了,几乎所有的验证点都需要使用这个方法。比如注册功能,在提交一些注册信息以后,一般都要到接下来的确认页面去验证一些信息,这些值都是动态的,在这里就能使用GetROProperty这个方法来动态获取实际运行时的一些确认信息,然后和所预期的测试数据做对比就可以了,这样就达到了自动化测试的目的。   总结:   属性对于自动化测试的重要性就不再老生常谈了,在刚接触QTP的时候,对于对象的封装属性都还不熟悉,此时Spy对我们来说就太重要了,用好Spy就可以轻松查到封装对象有哪些属性了,从而选择那些可利用的关键属性来完成自动化测试业务需求。除了Spy,只要有其他工具能探测到对象属性,那也是一样的,比如可以自行开发一个探测工具。在这里,也推荐给读者一个Spy的黄金搭档(只针对Web系统),那就是微软的IEDevToolBar,它同样也是一个探测工具,它可以轻松探测到节点和节点的属性名及其值,有兴趣的话可以网上搜索一下或者到微软的官方网站下载。   另外,相信读者应该已经发现,在QTP中使用封装方法对测试对象进行操作,只提供了SetTOProperty这一个方法来对属性进行设置和修改,其他的方法都是在做一些获取属性的操作。但是,SetTOProperty这个方法只能修改对象库中对象的值,那有没有修改实际运行时对象的属性的办法呢?答案将会在后面揭晓…… 1.6.3.3  对象封装属性的真正源头   在介绍Spy的那个章节中,我们就已经介绍过Identification Properties下会显示很多对象的封装属性,如图1-165所示。 图1-165   如图1-165所示,WebEdit控件的封装属性数量还是相当可观的,不光图中显示的这些,还可以通过右侧拖动滚动条看到更多的属性。这些属性都是可以通过GetROProperty方法进行访问的,这个在上一节中刚讲过,这里就略过了。在这里主要是介绍一个特殊的属性,它就是Class Name(见图1-165中第一个属性),这个属性在介绍Spy的那节中就已经介绍过。刚才说过了,凡是封装属性的值都可以通过GetROProperty方法去获得,那么,现在执行下面的这段代码,看看是否能够获得Class Name的值,预期结果应该是“WebEdit”(见图1-165): msgbox Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("wd").GetROProperty("Class Name")   待QTP执行完这段代码以后,我们一起来看执行后的结果,如图1-166所示。 图1-166   为什么获得的值是个空值?应该是“WebEdit”。图1-165中都是这么显示的。其实是因为QTP封装此属性的名称不是Class Name,而是micClass,不信?那我们执行下面这段代码再试试看,是不是会获取到“WebEdit”这个值: msgbox Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("wd").GetROProperty ("micClass")   QTP开始执行,结果如图1-167所示。 图1-167   如图1-167所示,结果已经看到了,就是想要的预期结果。此时,读者一定会问,为什么是micClass,而不是Class Name呢?为什么作者就知道是micClass呢?连Spy里显示的都是Class Name啊!别急,答案很快就会揭晓! 首先,我们运行regedit来打开注册表,然后进入到以下位置: HKEY_CURRENT_USER -> software -> Mercury Interactive -> QuickTest Professional -> MicTest -> Attributes   当展开Attributes这个目录时,会看到很多熟悉的属性。没错!这里就是QTP所有封装属性的集结地,如图1-168所示。 图1-168   在这个目录下可以找到micClass属性(见图1-169),而Class Name属性是没有的,所以刚才打印出来的值是一个空值就是这个原因。 图1-169   另外还有很多QTP隐藏的封装属性,并且这其中一部分属性在QTP的所有帮助文档中都没有提到过。在这里,作者就随便找一个Spy中没有的封装属性来试试,以source_index为例,来看下面这个脚本: '获取封装属性source_index的属性值 index = Browser("百度一下,你就知道")._ Page("百度一下,你就知道").WebEdit("wd").GetROProperty("source_index")  '在这里对WebEdit控件使用了描述性编程就是为了证明source_index是个隐藏属性,Spy中是没有的,关于描述性编程,读者先不用关心,后续章节中会详细介绍 Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("source_index:=" & index).Set "QTP自动化测试技术领航"   执行上面这个脚本后,就可以看到百度搜索框成功输入了“QTP自动化测试技术领航”。它的原理主要是通过source_index获取对象的索引,并通过描述此属性成功对此对象进行控制。   通过这个实例,我们终于明白了对象封装属性的真正源头到底在哪里!在这个目录下不光可以看到一些平时所常用的一些隐藏属性,例如,creationtime、index等,还有很多从来没有看到过的隐藏属性,有兴趣的读者可以每个都去尝试一下。   注意:每个属性都是有对应的对象的,比如creationtime属性只能用在Browser对象上,这点需要读者认知的。 1.6.4  梳理运行时对象的封装与自身接口的属性区别 1.6.4.1  解读对象的自身接口   在上一节中,作者已经介绍过实际上运行时的对象是有两种接口的,第一个就是上一节中主要讲解的对象封装接口,另一个就是将在本小节中重点介绍的对象自身接口。首先让了解一下两种接口的区别。   封装接口是对象的不完全属性,有些对象的属性值是封装接口无法获取得到的。   运行时的对象(RO)可以看见自身接口的所有属性,而对象库中的测试对象(TO)只可以看见被封装的一些接口。   封装属性是不能修改实际运行时的对象的,只能获取属性的值,而调用自身接口就可以。   以上3条就是封装属性与自身接口属性两者间的区别,其实读者应该不难发现这两者之间有本质上的一个特性,那就是封装接口既支持(或者叫涵盖吧)对象库中的测试对象,同时也支持运行时的对象,而自身接口就只支持运行时的对象,这点千万要注意,也不要搞混了,对于初学者来说这是一个难点和容易混淆的地方。 1.6.4.2  教你如何成为一名黑客   虽然现在读者已经了解了对象分测试对象与运行时对象,运行时对象除了有封装接口之外还有一个自身接口,但是这些都是概念上的内容,相信读者还是不明白自身接口到底是什么。在这里作者卖个关子暂且不告诉大家,先来玩一个有趣的“魔术”,作者在这里扮演一个黑客的角色,将这本书的推广方“51Testing”给黑掉。在袭击51Testing官方论坛首页之前,还是先让广大读者再看一下,因为过一会它将会变得面目全非:   如图1-170所示,目前51Testing的论坛还是原样,一会,作者将“51Testing”网站给黑掉,如图1-171所示。 图1-170 图1-171   如图1-171所示,51Testing网站界面上的某些控件经过作者的“施法”已经被替换成了百度网站中的一些控件,一共发生了4处变化,已经在图1-171中标注,它们分别是。   (1)将WebButton控件“登录”改成“百度搜索”。   (2)将Image控件“51Testing的Logo”改成“百度的Logo”。   (3)将Link控件“加入51Testing(注册)”改成“加入百度(注册)”。   (4)将Link控件“登录”改成“baidu_login”。   读者现在一定是非常的兴奋(很正常,当初作者也是如此的兴奋),为什么作者的手段那么厉害,能如此轻易地攻击一个大型网站。首先,作者可以告诉广大新人,作者的“帮凶”不是别人,正是这个强大的自动化测试工具QTP。所以,作为新人的你是不是一下子对QTP产生了浓厚的兴趣?的确是这样的,一旦学会了如何去当一名“黑客”,你一定会对QTP兴趣倍增,而且一旦学会了黑客之道,也同时证明了你的QTP水平已经上了一个台阶。 接下来作者将这个“魔术”表演分享并分解给读者,首先作者展示实现这个“魔术”表演的QTP脚本代码如下: With Browser("51Testing软件测试论坛").Page("51Testing软件测试论坛")      .WebButton("搜索").Object.value = "百度搜索"      .Image("51Testing_Logo").Object.src = "http://www.baidu.com/img/baidu_sylogo1.gif"      .Link("加入51Testing(注册)").Object.innerText = "加入百度(注册)"       .Link("登录").Object.innerText = "baidu_login" End With   上面这个脚本就是完成这个“魔法”的“功臣”,现在就来揭晓为什么作者可以轻易地改变51Testing网站!其实,就是因为使用自身接口修改了正在运行时的对象,所以才出现了图1-171中的假象。在上一小节中作者就已经介绍过了封装对象接口和自身接口的区别,其最后一条说的就是“调用自身接口就可以修改实际运行时的对象的属性值”。不过,它的确是假象,只需要刷新网页,51Testing网站就恢复原貌了。不过如果不刷新网页,那么这个“魔术”还是继续有效。比如作者假设把“注册”功能屏蔽掉,那的确是不可能点击到“注册”按钮的。这个实验作者也将留给读者去尝试,作为本章节的练习内容。   在了解这个魔术背后的秘密之后,一起来看一下调用自身接口的公式。   ● 对象.object.自身属性,示例如下: Browser("百度").Page("百度").WebButton("百度一下").Object.innerText = "百度一百万下"。   分析:以上代码是将WebButton("百度一下")这个对象的自身接口属性innerText设置成其他属性值“百度一百万下”。   ● 对象.object.自身方法,示例如下: Browser("百度").Page("百度").WebButton("百度一下").Object.click。   分析:以上代码调用WebButton("百度一下")这个对象的自身接口的方法click,以自身接口的方式完成点击操作。   另外,自身接口不但可以设置运行时属性的值,同样也可以获取运行时属性的自身接口的属性值,先来看图1-172所示。 图1-172   现在,要通过调用自身接口的方式来获取动态运行时的“百度搜索框”的内容,示例代码如下: getContent = Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("wd").Object.value : MsgBox getContent   分析:以上代码是获取WebEdit("wd")这个对象的自身接口的属性“value”的属性值,然后弹出对话框显示出这个属性值,如图1-173所示。 图1-173   小提示:注意到上段代码中MsgBox…和getContent…处于同一行代码了吗?难道没有疑问吗?这样QTP为什么没有报错?其实答案就在于在代码中加了“:”冒号。冒号在这里的作用就是连接本不相干的两句或N句代码使其合成一句。如果不加冒号,那QTP一定会报错。   到此,相信读者一定会产生这样一个疑问,那就是如何知道获取content就是用value这个自身接口属性?其实这个完全是凭经验的,作者一开始也不知道,做过了就明白了。新人在平时可以多用Spy去了解对象的属性(自身接口的、封装的一个都不能落下)并逐一尝试,过不了多久就可以熟悉了。  既然获取的时候用value这个属性值,那么,如果要对搜索编辑框输入一个值该怎么操作呢?同样也是要用到value这个自身接口属性值。先让我们来回顾一下如何用封装接口进行赋值操作,代码如下所示: Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("wd").Set "Yu Jie"   紧接着,用自身接口的方式,代码如下所示: Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("wd").Object.value = "Jerome Yu"   最后,一起来见证是否调用自身接口也能为百度搜索框赋值,如图1-174所示。   在结束这个知识点的介绍前,作者再次强调自身接口是无法调用对象库中的对象属性值的,读者务必记住!此外,作者还要分享给读者一些需要注意的地方,相同的属性名在自身接口和封装接口中的显示有所不同,以最常见的INNERTEXT来举例,如图1-175和图1-176所示。 图1-175 图1-176   如图1-175和图1-176所示,我们可以看到,在自身接口中显示的是“innerText”,T是大写的,在封装接口中则显示的是“innertext”,全是小写。举个例子,假设前者innerText的值等于A,现在需要获取这个值,如果此时误写成innertext的话,获取的值将是一个空值,就完全与预期不一样了,所以,读者务必要注意到这点,一开始会很容易犯错,而这些小错误对于没经验的新人来说很难察觉到,因为代码并没有错,所以QTP也不会报错。 1.6.5  几种常见对象无法识别或识别错误的原因   对象库的整个学习过程即将结束了,无论是调用封装接口还是调用自身接口,它们都要有一个大前提,那就是对象能够被QTP识别。但是往往在自动化测试过程中,会碰到很多对象无法正确识别或识别错误的情况,作者进行了一下总结,以下几种原因是最常见的。   1.软件程序的对象控件无法被QTP识别   QTP毕竟不是万能的,很多软件的对象控件都无法识别到,尤其是C/S架构的软件程序,大多数控件都是WinObject。碰到这种情况,在第一章节中就说过了,此类项目是不适合用QTP做自动化测试的,当然如果必须要做自动化测试,通常有以下几种解决方案。   (1)设置虚拟对象。   先来知悉虚拟对象的方位,如图1-177所示。 图1-177 如图1-177所示,该位置就是虚拟对象,存在两个功能,新增虚拟对象和管理虚拟对象。作者在这里不对此功能多做介绍,因为并不推荐这个方法,即使依赖虚拟对象解决了所有不能识别的对象,但以后的对象维护工作是根本没法继续下去的,而且虚拟对象本身就非常脆弱,即使对象没有发生变化,但是,只要对象在界面上的方位发生变化,虚拟对象就会识别失败。所以,读者只需要了解一下这个功能就可以了,感兴趣的读者不妨尝试着亲自设置一些虚拟对象。   (2)使用相对坐标然后配合WSH去定位对象。   当碰到对象无法识别的时候,还可以通过这种办法尝试解决。因为即使是一个WinObject对象,它仍然有坐标,所以,使用相对坐标则可以帮助动态定位对象的位置,然后再使用WSH去对这个WinObject做一些操作(当然,这些操作不可能非常全面)。还比如,虽然对象无法识别,但是它支持快捷键操作,比如使用“Ctrl+O”的快捷键组合可以打开某个东西,遇到此类情况,也可以使用WSH来巧妙地完成想要完成的业务(虽然对象仍然是不能识别的,但是只要能达到预期目标,任何方法都是好方法)。关于WSH的知识点会在后续的章节中详细介绍。在本小节中,读者只需要知道常见的对象无法识别案例和相应的解决方案即可。   (3)使用DOM组件接口应用技术。   DOM的全称是Document Object Model。这种办法只可用于Web项目。举个例子,淘宝网的密码输入框用正常的Set方法是无法操作的,此时就可以调用DOM的GetElementById方法,然后进行密码输入的操作。DOM也会在后面的章节中详细介绍,这里不多做阐述。   (4)使用QTP自定义扩展SDK Customer来进行二次开发使QTP能够识别对象。   这个属于QTP中最高深的技术。目前关于此方面的技术文章在QTP学习网站中都是非常少的。本书的作者曾录制过两个教学视频介绍此方面内容,如果读者有兴趣可以联系本书的两位作者,在本书的末尾有作者的联系方式。   (5)开发提供专属插件。   作者曾经有过类似的自动化测试项目经历,对象基本都无法识别,但是自动化测试需要做下去。于是开发了专职对象识别脚本,然后利用QTP这个平台去执行自动化测试。像这种情况,其实自动化测试的主力就已经不再是QTP了。   (6)把无法识别的对象的一些方法封装到一个dll中并使用QTP调用。   这个方法和上面的有所区别,自动化测试主力仍然是QTP,在QTP中有一个Extern对象,这个对象就是专门司职外部dll调用工作的,开发只需要把各种方法封装成一个dll即可,关于调用外部dll的技巧同样会在后面的章节中介绍到。   2.对象可以被QTP识别,但是加载的插件不正确   发生这种情况总体来说还是幸运的,因为毕竟这也许只是一个误操作,只需要重新加载相对应的正确插件,QTP就可以为你服务了。   3.同一个界面中存在两个或两个以上的属性相同的对象   有这么一种情况,同一个页面中存在多个属性相同的对象,这种时候QTP会报错并在报错信息中提示我们。像遇到这种问题时,最直接的解决办法就是为每一个对象做一个唯一标识符,最常见的就是设置Index。   4.实际运行时的对象发生了改变导致与对象库不匹配   在自动化测试过程中,很多对象都是动态的,在运行时,属性是一直在发生变化的,比如句柄。碰到类似的问题,解决思路就是先动态获取运行时的值,然后将这个值动态添加到对象库对象中,这样就可以轻松解决该问题了。如何动态获取运行时的值,如何将值动态添加到对象库对象中,这些都是前面的重点内容,读者如果有所遗忘,请重新回顾! 1.6.6  总结   本章节和上一个章节不同,主要是围绕如何去实现脚本。这两个章节合起来几乎涵盖了对象库编程所有的精华部分,读者务必要学精、学透。作者已经给了方法,接下来就是要通过大量的实践和反复的练习真正地驾驭好它。   对象库编程的确是相当好用的,QTP高效编程的3种方式也都是基于对象库的,这些高效的方法是不基于在下面的章节会介绍的描述性编程的。所以,一般情况下,作者建议使用对象库编程。 知识点巩固和举一反三练习   一、过把“魔术师”或黑客的瘾!   要求1:根据图1-178和图1-179中的数字标识依次按要求慢慢“吞噬”百度首页。 图1-178 图1-179   (1)将Title和Tab变成“QTP自动化测试技术领航”。   (2)将Logo变成51Testing首页的Logo,URL为www.51testing.com。   Logo图片来源:进入51Testing首页后,右键单击网站Logo后,在弹出的菜单中点击属性,复制URL,如图1-180所示。 图1-180   (3)将贴吧变成“51Testing专家博客”。   (4)将搜索框禁用(无法输入任何文字)。   (5)将[百度一下]按钮变成“51搜索”。   (6)将“把百度设为主页”变成“把51Testing设为主页”。   (7)将“加入百度推广”变成“加入51Testing推广”。   (8)将“关于百度”变成“关于51Testing”。   (9)将“About Baidu”变成“About 51Tesing”。   (10)将该处文字替换成“2011 51Testing测试自动化系列丛书-QTP自动化测试技术领航”。   要求2:提取公共部分代码,提高代码维护性、直观性。   二、分别使用已学过的QTP编程的3种方式来完成第一题。   要求1:熟练运用并掌握,然后评价出你觉得最适合你的一种方式。 1.7  描述性编程(Descriptive Programming)   阶段要点   ● 描述性编程不高深。   ● 描述性编程的两种写法。   ● 描述性编程实例介绍。 1.7.1  一点都不高深的描述性编程技术   QTP刚进入国内不久时,各大测试论坛曾经有过一场持续了多年的争论,引起这场争论的导火线就是对象库编程(以下简称ORP)和描述性编程(DP)。这场争论持续了至少3年以上。争论的话题都是使用QTP进行自动化测试,其测试脚本是使用对象库编程好还是描述性编程好,有兴趣的读者完全可以在51Testing论坛中输入一些关键字进行搜索,如“对象库描述性编程”等,相信应该还能重新找到不少信息。   大约从2009年开始,随着大家对QTP技术的了解,ORP与DP的争论已经越来越少了,因为随着时间的推移,国内的QTP自动化测试技术也发展到了一定高度,使得越来越多的测试人员更倾向于ORP!为什么?ORP技术为什么好过DP技术?有什么依据?暂时先不说,先说说大多数当初支持DP的人们的一些心态:   第一种也是最典型的一种,就是描述性编程这个名词里嵌套了“编程”两字,这得怪Mercury开发QTP的时候提出了DP这一概念!这“编程”两个字可误导了不少测试新人。相信大家都知道,国内测试行业的很多新人大多都是其他行业转型过来的,相对做测试的门槛没有做开发那么高,很多也没有经受过系统的计算机软件方面的学习,所以对“编程”一直很向往,觉得编程很难,所以一听到QTP的描述性编程这个概念,就觉得是个很高深的技术。在早期,就是因为这种心态使得一大批测试新人在慢慢熟悉并会使用QTP以后,明明可以不去使用DP也硬要去使用,以显示自己是技术牛人,其实这种举动是化简为繁并脱离了自动化测试的本意。当然,很多支持DP的人不是属于炫耀范畴的,是属于被“编程”两字忽悠了的范畴的。   第二种也是比较典型的,因为以前大家都认为DP很高深,觉得DP是QTP中的精髓。所以,如果使用DP写脚本并提交给测试经理看,那领导一定会认为你很牛。很明显,以上两种都是稍许带有贬义的。   第三种争论的焦点是自动化测试框架,配合自动化测试框架,在进行QTP编程的时候到底用ORP好还是DP好?这个也可以说是3个中唯一一个真正对国内自动化测试领域的提高有价值的争论,因为的确是各有好处的!关键不是使用哪种QTP编程技术,更多的焦点是框架设计的怎么样。   所以,排除第三种自动化测试框架的特殊情况,选择ORP技术的人们是理智的人群,咱也不能说选择DP的人们就是不理智的,但是可以肯定,如果明明可以使用ORP技术却还硬要使用DP技术的人们是肯定不理智的。   很自然的,对象库编程就是我们上一章节刚刚重点介绍过的非常强大的一个功能,而描述性编程就是本章节要介绍给各位新人读者的一项编程技术。其实呢,本人一开始听到QTP的描述性编程技术的时候也一直以为是一门很高深的技术。事实上……下面就会用实例证明给大家看,其实DP就是这么简单的一回事!现在基本上大家都认可了ORP是QTP自动化测试的首选,为什么?因为ORP的确经得住考验,而且在下面的“终极对决”的小节里,还会让ORP和DP来一次大“PK”来证明,为什么选择ORP是理智的,为什么大家最终还是倾向于ORP,并且还会介绍一下ORP相比DP的一些优越性。 1.7.2  掌握描述性编程的两种写法   首先,需要用最简单和生动的例子来介绍描述性编程的概念,或者说它到底具体是什么,我们仍然用百度页面,请先看下面图1-181以及代码段。 图1-181   相信读者应该都已经很熟悉图1-181了,它是一个对象库,里面添加了一些需要操作的百度网站的对象。接下来我们来看代码的实现: Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("wd").Set "QTP自动化测试技术领航" Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click 以上这段代码读者应该也已经很熟悉了。它就是用对象库编程产生的代码。但是,读者有没有思考过一个问题,假设这些对象没有添加到对象库里怎么办?QTP还能工作吗?完全可以!QTP提供了描述性编程(Descriptive Programming)这个解决方案。事实上,在很多情况下,对象都不会顺我们的意思,经常会面临“不是我们想不想把对象添加到对象库,而是根本没法添加”这种尴尬局面。所以,此时“DP”可以担起重任了,替“ORP”完成它无法完成的事!这里暂时先不介绍无法添加对象的案例,我们先存心使对象无法生存于对象库中。把之前添加好的百度的对象全部删除,如图1-182所示。 图1-182   就像我们现在看到的一样,已经没有对象供我们使用了,怎么办?当前需要做的唯一的一件事是“照搬”!搬什么?虽然对象库里的对象没了,但是我们完全可以将对象库中的内容直接搬迁到脚本中去。又是什么内容是需要搬迁到Expert View中去的呢?—就是我们所要用的对象的一个个属性及其属性值,一起来看下是怎么搬迁的,推序如下所示: '代码1 Browser("micClass:=Browser").Page("micClass:=Page")._ WebEdit("name:=wd").Set "QTP自动化测试技术领航" '代码2 Browser("micClass:=Browser").Page("micClass:=Page")._ WebButton("name:=百度一下").Click   描述性编程大解析(第一种)。   整个搬迁过程完成了,这就是描述性编程,最简单地说,DP无非就是在描述每个对象的属性和属性值,通过这个原理来虚拟成对象库中的对象,只是对象库是隐形的。   那先来看看第一句代码(代码1):描述性编程的运作原理完全是和对象库编程一致的,所以在这里先去描述一个最“上层”的对象“Browser”,对象名称写好以后用上一对括号,然后在括号里依次从左到右填入引号、一个属性名称、一个冒号、一个等号、一个属性值、引号。这个就是第一个形式,即:对象名("属性名:?=?属性值")。读者必须要记住这个形式(描述性编程的形式一共有两种,在后文中会继续介绍第二种),有了前面的描述模板,接下来就完全可以依葫芦画瓢了。按照对象的结构顺序一层一层地往下描述,直到定位到最终想要操作的对象,最后给它一个方法,整个过程就结束了。看脚本中的代码1就是这样的。所以,同样的道理在代码2中就不讲了。   再总结一下,其实描述性编程就是将原对象库编程中括号内的“对象名”(见图1-183)换成一种描述语言,它描述的仍然是这个对象,只不过不再是封装好的现成的对象,而是需要现场描述(封装)。 图1-183   第一种描述形式已经介绍完了,但是关于第一种描述性编程方式的内容还没有讲完,在前面那段代码中,都是以单属性及其属性值来描述一个对象。其实在QTP中,还可以同时描述多个对象,但是数量还是会有一个极限的,可以描述的属性必须是QTP内置的(怎么才能知道哪些属性是QTP内置的,可供我们描述的,以及一些属性值的设置等,都会在下一小节的内容中介绍到),先让我们一起来看下面这段脚本: '代码1 Browser("micClass:=Browser").Page("micClass:=Page")._ WebEdit("html tag:= INPUT","name:=wd").Set "QTP自动化测试技术领航" '代码2 Browser("micClass:=Browser").Page("micClass:=Page")._ WebButton("html tag:= INPUT","name:=百度一下","type:= submit").Click 上面这段代码中,同样也是两句代码,第一句的最终定位对象用了两个属性去描述,第二句的最终定位对象则用了3个属性去描述。在这里同样要告诉读者们必须记下来的一个规则,那就是描述多个属性时,属性间用逗号隔开,这个逗号必须是英文状态下的。另外,描述性编程的语法是对了,但是如果描述的属性值没有设置对,那QTP是不会执行的,例如,假设将上面这段代码中的第二句代码中的最终对象“WebButton”所描述的属性type改成“error”这个值,其结果读者可以自行去尝试下,看看QTP会不会执行。   还要让读者记住第二条规则,那就是如果父对象描述了,子对象则一定要描述,不然QTP会报错,来看下面这个示例脚本: '正确 – 父对象如果描述了,子对象必须描述 Browser("micClass:=Browser").Page("micClass:=Page")._ WebEdit("name:=wd").Set "QTP自动化测试技术领航" '错误 – 父对象描述了,子对象没有描述 Browser("micClass:=Browser").Page("百度一下,你就知道")._ WebButton("百度一下").Click   图1-184是父对象描述后子对象没有描述导致QTP报错的截图。 图1-184   但是子对象如果描述了,父对象可以不描述,当然,父对象不描述又不报错的前提是要被添加到对象库中,一起来看下面这个示例脚本: Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("name:=wd").Set "QTP自动化测试技术领航"   至此,第一种描述性编程方式的基本内容全部介绍完毕。   描述性编程大解析(第二种)。   现在要介绍第二种描述性编程方式,那就是使用Description对象。使用该对象可以返回包含一组Property对象的Properties集合对象。Property对象由属性名和值组成。然后,可以在语句中指定用返回的Properties集合代替对象名(每个Property对象都包含一个属性名和值)。   要创建Properties集合,需要先创建Properties对象,使用以下语法进行: Set ObjDescription = Description.Create()。   创建完毕后,就可以在运行会话期间在Properties对象中添加、编辑、删除或检索属性和属性值了。也可以这么理解,就是将对象的属性及其属性值的描述封装在一个特殊的Description对象中。例如,假设现在需要完成以下这个操作,见下面这个脚本: Browser("micClass:=Browser").Page("micClass:=Page")._ WebEdit("html tag:= INPUT","name:=wd").Set "QTP自动化测试技术领航"   完全可以通过Description对象来实现同样的功能,参见下面这段代码: Set ObjBrowser = Description.Create() ObjBrowser("micClass").Value = "Browser" Set ObjPage = Description.Create() ObjPage("micClass").Value = "Page" Set ObjWebEdit = Description.Create() ObjWebEdit("html tag").Value = "INPUT" ObjWebEdit("name").Value = "wd" Browser( ObjBrowser ).Page( ObjPage ).WebEdit( ObjWebEdit )._  Set "注意此时描述对象的括号内是不需要加引号的,加了引号反而就错了!" '最后需要记住释放对象,可以从最里面一层开始释放直到最外面一层 Set ObjWebEdit = Nothing Set ObjPage = Nothing Set ObjBrowser = Nothing   两种描述性编程方式都已经介绍完了,本人认为第一种更适合应用于普通脚本中,或者这么说,在对象库编程无法完全任务的时候,描述性编程临时加上一句,这样做显得更加直观,代码数量也更加少。但是很明显的缺陷就是无法做到复用。第二种描述性编程的方式,个人认为更适合应用于基于框架的脚本中,从表象上看虽然比前者会多写几句代码,但是这种方式的复用性远远优于前者,所以,具体选取哪一种方式都应按项目的实际情况界定。 1.7.3  Object Identification与Spy结合DP的妙用   在前面的章节中,已经认识了Object Identification和Spy,在本小节中,这两位老朋友又要粉墨登场了。它们不止可以与对象库编程结合,同样可以和描述性编程结合。接下来,就分别聊聊它们与描述性编程的默契搭配。   在前面的小节中已经将描述性编程的语法教给大家了,语法是固定的就两种,但是描述的属性就非常多了,相信很多读者都已经开始疑问或者迷茫了,怎么知道哪些属性可以描述,全背出来了?其实不可能啦!本书在一开始的章节中就提到学习QTP是任何知识点都不用背的。所以,在这里本人公布:首先介绍的是Object Identification。通过O\I就可以知悉一切对象可描述属性,如图1-185所示。 图1-185   如图1-185所示,这就是O\I的界面,应该很熟悉了吧,之前都已经认识了,所以在这里一些基本介绍就不重复了。现在选中的就是刚才描述过的一个控件“WebButton”,默认出现在Mandatory Properties窗口里的就是系统默认的一些该控件的重要属性,如果要描述一个控件,首先先描述这些最重要的属性(也许这种说法不科学,但是本人觉得这说法很实际和现实)。如果默认的属性满足不了你的需要,那你只需点击Add/Remove就可以了,里面可以让你新增或删除一些其他的属性,如图1-186所示。 图1-186   当然,不但可以删除一些备选的属性,也可删除系统默认属性。另外,在此主要要讲一下Browser和Page对象的描述,先来看看O\I吧,如图1-187和图1-188所示。 图1-187 图1-188 我们可以看到,Browser和Page是没有系统默认属性的。因为这两个控件比较特殊,一般情况下,都会使用Class Name或者creationtime这两个属性来描述该对象,它们在O\I里可是没有的。Class Name可以从Spy里看到,说到Class Name我们也不得不回顾一下之前讲过的一个挺重要的知识点,就是Class Name必须要写成micClass。读者再返回看看在上一个小节中,描述Browser和Page的时候是不是使用了micClass?   接下来讲解的是Spy,Spy在描述性编程中也是经常被调用的,因为O\I用于定位所要描述对象的属性,而Spy就是定位描述属性的属性值,它们绝对是默契的搭档!描述性编程也就是因为有了它们,才可以应用起来,如来看看下面这张Spy图,如图1-189所示。 图1-189   本小节的内容相对比较简单,不过简单不代表实用性就不高,恰好是相反的!最后,告诉读者描述性编程可描述的属性都是封装接口属性,不是自身接口的属性,务必切记! 1.7.4  描述性编程的妙用以及与对象库编程的混搭   本人是不支持使用纯描述性编程的,DP的使用应审时度势且以实用的角度出发。前面也讲到过了,一般情况下,描述性编程应用在对象库编程无法完全满足需求的时候,以替补的身份去出色完成少额的任务。但是要遵循一个原则,那就是不到必不得已(对象库编程实在没法解决),绝对不要去使用描述性编程,千万要杜绝想使用了就随意使用的情况。这样做有什么好处?最大的好处就是可以使脚本一目了然从而增加了可维护性!当一段脚本中都是对象库编程,突然有一行出现了描述性编程,那即使过了几个月后再看脚本,我们也可以马上明白,当前步骤是比较特殊的,是对象库编程完成不了的。反之,如果不养成这个好习惯的话,那整个脚本的层次就会显得非常乱,时间久了一定分不清哪些使用描述性编程的语句属于没必要范畴,而哪些属于必要范畴。   接下来,就举一些项目中需要使用到描述性编程的案例。   (1)百度首页有很多相同的Link控件,如“新闻”、“网页”、“图片”等,全部添加到对象库很麻烦,那么该如何使用描述性编程完成?   首先,先来看下场景图,如图1-190所示。 图1-190   一个首页就有接近?20?个相同类别的控件(Link),虽然不多,但是一个个添加也够烦锁的,既然它们是完全相同类型的控件,那么使用描述性编程是一个上佳之选,下面来看这段脚本,看看是如何实现的: Set baidu = Browser("micClass:=Browser").Page("micClass:=Page") Print Baidu.Link("name:=新闻").Exist With baidu         Print .Link("name:=贴吧").Exist          Print .Link("name:=知道").Exist          Print .Link("name:=MP3").Exist          Print .Link("name:=图片").Exist          Print .Link("name:=把百度设为主页").Exist          Print .Link("name:=搜索风云榜").Exist          Print .Link("name:=About Baidu").Exist  End With Set baidu = Nothing 执行脚本以后的结果就是在Log窗口内写8行True,返回True就说明描述的对象存在了,也就说明描述性编程成功了,如图1-191所示。 图1-191   分析。   这段脚本首先用Set将公共部分进行了提炼,这样可以使重复的部分合为一个整体。然后完全还可以进行优化,所以,选择使用With将所有会被复用的代码提炼出来(这里指Baidu),这样整个脚本就显得非常清晰了。   这也是描述性编程常用情况之一的最基本的一个情况:同一个界面中出现很多个相同类别的控件元素。   (2)如果要同时操作浏览器的多个窗口时,怎么做?你想过吗?   通常情况下,都只需要在一个窗口中完成任务。如果同时出现两个窗口的话,QTP就会出错,因为QTP匹配到了大于1个的窗口对象,所以它不知道究竟该对哪个具体对象进行操作了。所以,此时就要用以下这个方法,脚本如下所示: SystemUtil.Run "C:\Program Files\Internet Explorer\IEXPLORE.EXE"  SystemUtil.Run "C:\Program Files\Internet Explorer\IEXPLORE.EXE"  Browser("CreationTime:=0").Navigate "http://www.51testing.com" Browser("CreationTime:=1").Navigate http://www.baidu.com   使用以上代码,QTP就能够分辨出多个浏览器窗口了,当然,也可以使用Index或Location属性,大家可以尝试一下。同时,也可以尝试一下如何将指定的窗口关闭。   另外,当使用Browser ("CreationTime:=-1")的时候,表明当前有且仅有一个浏览器窗口,当只需要一个浏览器的时候,可以使用这个方法来作为判断依据,脚本如下所示: objBrowser = Browser ("CreationTime:=-1").Exist (0)  If objBrowser Then      Msgbox "只存在一个浏览器窗口"  else      Msgbox "存在0个或多个浏览器窗口"  End If   (3)使用描述性编程通过遍历对象完成N个同类控件的操作。   假设有这么一个场景,页面中有几百个输入框,此时如果逐一将这几百个对象添加到对象库是不科学的,使用描述性编程则是一个明智选择。但是,也不能逐一描述,因为效率同样的低。在这种情况下,就可以用描述性编程来遍历页面中的对象,从而最终完成艰巨的任务。百度的高级搜索页面就是一个比较典型的例子,页面中有很多输入框,如图1-192所示。 图1-192   现在要对这些输入框做文章,在每个WebEdit中输入“QTP自动化测试技术领航”这段字符串,实现脚本如下: '打开网站页面 SystemUtil.Run "C:\Program Files\Internet Explorer\IEXPLORE.EXE",_ "http://www.baidu.com/gaoji/advanced.html" '描述对象 -- WebEdit Set all_oEdit = Description.Create  all_oEdit("micClass").value = "WebEdit"  '为WebEdit找父对象和祖父对象,并将所有对象“包装”在一起 Set all_oEdits = Browser("micClass:=Browser").Page("micClass:=Page")._ ChildObjects(all_oEdit)  '遍历页面中的WebEdit对象,找到一个就输入一串指定的字符串 For i = 0 to all_oEdits.count - 1      Set oEdit = all_oEdits.item(i)      oEdit.Set "QTP自动化测试技术领航"  Next '最后记得释放所有设置的对象 Set oEdit = Nothing Set all_oEdits = Nothing Set all_oEdit = Nothing (4)QTP自带的网上订票系统是一个非常经典的描述性编程例子   众所周知,在QTP里自带着一个网上订机票的网站,也就是Mercury Tour。现在一起来看一下在订票过程中何时需要使用描述性编程。   首先登录系统后,如果需要订票的话,就要先搜索航班,此时系统要求输入订票乘客的数量,假设在第一次写脚本的时侯将Passengers设置成了1,然后成功地完成了订票。然后,需要参数化乘客数量来测试订票系统,我们会发现QTP回放失败了。其根本原因在于,乘客的数量已经变化了,导致在订票时需要输入每个乘客的姓名,而在写第一个脚本的时候,只设定了一个乘客的姓名。乘客姓名的输入框是随着乘客数量的变化而动态生成的,我们不可能从对象库里得到没有添加过的对象(如果是100个乘客,那需要事先将100个乘客对应的姓名输入框添加到对象库里,显然这是不可取的)。因此,必须使用描述性编程来完成这个业务。现在,我们假设将乘客数量设定为2,如图1-193所示。 图1-193   然后来看一下设定单个乘客时的脚本如下所示: Browser("Welcome: Mercury Tours")._ Page("Book a Flight: Mercury").WebEdit("passFirst0").Set "FirstName" Browser("Welcome: Mercury Tours")._ Page("Book a Flight: Mercury").WebEdit("passLast0").Set "LastName"   然后到对象库里看看WebEdit控件,可以看到对象的属性如图1-194所示。 图1-194   当系统对于发生多个FirstName时,命名规则是passFirst0,passFirst1…依次类推。因此,我们现在只要通过描述性编程就可以完成动态FirstName与LastName的识别工作了。假设参数化的乘客数已经赋值给intPassNum = 3,那么在此,描述性编程脚本就该这样写: For i = 0 to 3    Browser("Find a Flight:")._    Page("Book a Flight:").WebEdit("name:=passFirst"& i).Set "FirstName"    Browser("Find a Flight:")._    Page("Book a Flight:").WebEdit("name:=passLast"& i).Set "LastName" Next   再举一个和这个经典例子差不多的案例,比如现在有一个订书的网站,有一个输入框可以输入想要订购的数量,输入1就会出现1个输入书名的文本框,代码是:WebEdit("name:=book1");输入2就会出现2个输入书名的文本框,代码是WebEdit("name:=book2")…同样也是依此类推。像碰到这类情况,最好的解决方案就是描述性编程,代码可以是:WebEdit("name:= book"& i)。   小结:以上这些都是对象库编程搞不定或者不适宜搞定的案例。在真实的项目中一定还会有形形色色的案例,在此无法全部列举。但是完全可以举一反三,善加利用描述性编程,从而可以出色地辅助我们完成各种自动化测试需求。 1.7.5  终极对决—对象库编程(OP)?VS描述性编程(DP)   关于对象库编程和描述性编程的学习就要接近尾声了。临近结束之时,再一起来分析一下对象库编程和描述性编程各自的优势,也即知己知彼百战百胜。   对象库的优势。   (1)可以通过Complete Word、“F7”等多个方式进行高效编程。这个特性描述性编程没有。   (2)对象库编程有一个比较好的特性,假设脚本中引用了同一个对象10多次,这个对象的名字之前取得不是太出色,项目经理要求改名。此时不需要改10多次,只需要进入对象库,对这个对象进行更名,脚本便会批量自动更新,很高效!这个特性描述性编程也没有。   (3)对象库编程不容易打错字,因为有Complete Word,想打错字都难。但是,描述性编程没有Complete Word,所以,打错字是家常便饭。关键是,对于一个新测试员来说,他不可能有敏锐的分析手段,往往因为这么一个小错别字会浪费很多时间。其实只是一个错别字而已,往往最后被误解成脚本发生了错误,无论怎么调试都看不出来。   描述性编程的优势。   (1)不用维护庞大的对象库,不过需要维护庞大的代码。所以这算是优势还是劣势?请读者感悟。作者在此提一句,其实在对象库功能做得如此智能的情况下,维护好对象库不难,只要根据在“对象库”那个章节中介绍的一些法则,如命名规范等就可以管理好对象库。   (2)描述性编程可以完成一些特殊的需求(上一个小节的主讲内容)。   最后的PK结果:对象库编程获胜! 1.7.6  总结   描述性编程的学习全部结束了,它是一个很好、很优秀的功能,但同时也是一把双刃剑。最大的问题就是由于脚本无法维护导致自动化测试项目的失败。   通过本章的学习,希望读者不光可以掌握描述性编程的技能,更应该了解它的缺陷,不要盲目崇拜“编程”这两个字,开个玩笑的说:“描述性编程,不好惹!”   自动化测试的目的是使测试自动化起来,不是一种炫耀,本人也一再强调过:能做好自动化测试项目的自动化测试才是好的、成功的。所以,不到必不得已,请远离“描述性编程”!   在对象库编程和描述性编程的学习结束之际,也就意味着广大新人读者已经完全可以独立去写一些自动化测试脚本了,已经逐步开始有能力成为一名“QTP自动化测试工程师”,已经彻底摆脱了录制化的QTP,这是一个里程碑!   知识点巩固和举一反三练习   素材: title = Browser("百度一下,你就知道").Page("百度一下,你就知道").Object.title Msgbox Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").GetROProperty("value") Browser("百度一下,你就知道").Page("百度一下,你就知道").Link("关于百度").Click   一、使用基础方法描述素材中的代码。   二、使用Description.Create方法描述素材中的代码。 1.8  数据池(Data Table)的应用   阶段要点   ● 熟悉测试数据和脚本业务分离的好处和优势。   ● 学会利用Data Table将测试数据与业务分离。   ● Global Sheet与Local Sheet的区别。   ● Test DataTable  VS  Run-time DataTable。   ● DataTable常用方法指引。 1.8.1  引言   作为一个QTP自动化测试工程师,学会使用Data Table是必不可少的。Data Table其实和Excel非常相似,几乎可以说是“克隆”出来的。所以,读者会上手的很快。   虽然上手会比较快,但是也别小看了它,因为在自动化测试过程中,数据之间的传递是非常重要和常用的,小小的一个Data Table可绝对是自动化测试的主力和核心组件。在前面已经学会了整个对象库的使用。那么在本章节就让我们来一起领略Data Table是如何管理好庞大而且繁琐的测试数据的、是如何进行不同测试数据的传递的…… 1.8.2  学会使用DataTable进行参数化 1.8.2.1  为什么要进行参数化   首先,从最简单和常见的说起,相信大家对参数化这个词非常熟悉,在本书的前面也提到过,不过并没有细致地讲解如何参数化。因为,QTP内置的参数化功能其实在真实项目中是不会去使用的。那如何去参数化测试数据呢?答案就是利用这个Data Table,使用它配合代码来管理好繁杂的测试数据才是自动化测试的正道。接下来,让我们进入第一个实例,先来看下面这段代码: SystemUtil.Run "www.baidu.com" For i = 0 to 9    Browser("百度一下,你就知道").Page("百度一下,你就知道")._    WebEdit("关键字输入框").Set "QTP自动化测试技术领航"    Browser("百度一下,你就知道").Page("百度一下,你就知道")._    WebButton("百度一下").Click    Browser("百度一下,你就知道").Page("百度搜索_搜索结果页面")._    Image("到百度首页").Click Next   然后,图1-195是脚本对应的对象库,读者可以看一下。 图1-195   最后,我们解读一下脚本:这段脚本其实还是比较简单的。先打开一个浏览器,然后进入百度的首页,接着输入关键字“QTP自动化测试技术领航”,最后点击“百度一下”按钮。这样,百度会进入搜索匹配页面,此时便可以开始做很多测试验证。当然,这里验证省略掉了,因为这是后面章节的内容。那我们假设验证是通过的,成功匹配到了“QTP自动化测试技术领航”的一些相关信息,那么通过点击“到百度首页”这个Image对象回到首页,因为需要再一次输入测试数据,再进行N次测试,测试什么?就是测试关键字输入后点击搜索能否正确搜索到相关的信息。所以,在这里写了一个循环。这样,QTP就能测试10次了,这个结果应该很可靠了吧?如果10次都通过了,那这个功能就应该没问题了。 单从表面上看,的确是!但是,同样面临一个问题,那就是在用同一个测试数据“QTP自动化测试技术领航”在做测试。所以,执行1次和执行上百次是没有什么实质性的区别的,特别像这种搜索框之类的功能点,这样的自动化测试也算是一种浪费时间的无效测试(特殊情况除外,如可靠性测试)。怎么才算有效?从手工测试的角度上来说,就是输入完全不同的测试数据,然后查看测试结果。那QTP该怎么做呢?很简单,只要把关键字输入框中Set的数据改掉就可以了,如下面这个脚本: '启动浏览器并进入百度首页 SystemUtil.Run "www.baidu.com" '第一次 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set "QTP自动化测试技术领航" Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click Browser("百度一下,你就知道").Page("百度搜索_搜索结果页面")._ Image("到百度首页").Click '第N次 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set "谷歌" Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click Browser("百度一下,你就知道").Page("百度搜索_搜索结果页面")._ Image("到百度首页").Click '第十次 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set "最后一次了,相同的代码一共使用了10遍" Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click Browser("百度一下,你就知道").Page("百度搜索_搜索结果页面")._ Image("到百度首页").Click   上面这段代码最后完全实现了想要的效果,但是,缺点大家都已经看到了,有太多的重复内容,假设是要输入100个测试数据呢?复制、粘贴100次吗?显然不需这样,完全可以将Set的值进行参数化。先给出第一个解决方案,使用前面第一段代码相同的循环,但又每次输入不同的关键字,代码如下所示: SystemUtil.Run "www.baidu.com" For i = 0 to 9   '句1:代码唯一有改变的一句    Browser("百度一下,你就知道").Page("百度一下,你就知道")._    WebEdit("关键字输入框").Set DataTable.Value("关键字输入","Action1")&i    Browser("百度一下,你就知道").Page("百度一下,你就知道")._     WebButton("百度一下").Click    Browser("百度一下,你就知道").Page("百度搜索_搜索结果页面")._    Image("到百度首页").Click Next   我们来分析一下这个脚本,其实这段代码几乎和先前的那段代码没有两样,作者只改动了一句而已(代码里用注释标注的句1)。解决方案其实也不难,就是把每次循环的计数和测试输入组合,这样每个测试数据就都不一样了。不过,读者应该发现了吧,在这里并没有直接将测试数据明确写出来,取而代之的是DataTable.Value("关键字输入","Action1")这句话。这个就是本章节介绍的参数化,通过DataTable参数化。   作者把测试数据封装到了DataTable.Value("关键字输入","Action1")里,如图1-196所示。 图1-196   如图1-196所示,这个就是QTP内置的Data Talbe界面,可以在这里输入一些数据。把测试数据“QTP自动化测试技术领航”输入到了A1这个单元格里。这样做有什么好处?首先,一定知道现在所做的事情的意义和意图。其实从某种角度上讲,虽然操作很简便,但是,我们是在做封装的动作,将测试数据独立出来,并封装到一个容器里,以后可以供脚本调用。为什么要封装并且要把测试数据独立出来存放到一个储存的地方?试想一下,如果不进行测试数据封装会有什么后果。比如脚本里有100处地方需要输入这个测试数据,如果突然有一天测试需求变化了,不再需要输入这个关键字,需要换另一种测试数据进行输入,因为需要修改100处地方!如果此时测试数据和脚本是分离的,那我们只需要在A1这个单元格修改1次就可以了!1:100哪个划算,相信读者和本人一样清楚!到此,也引出了自动化测试的一个重要理念:测试数据和脚本业务的抽离。当然,将测试数据剥离出脚本的方法有很多种,在这里只介绍如何利用QTP的Data Table进行测试数据分离。 1.8.2.2  如何具体操作   在上个小节中,已经介绍了将测试数据与脚本业务分离的知识,并且读者也看到了数据分离后的情况。那么接下来,一步步地介绍具体该如何去完成数据与业务相分离的操作。在开始前,首先一起来看一下Data Table的原始情况,如图1-197所示。 图1-197   如图1-197所示,可以看到当前一共有两个Sheet,它们分别是Global和Action1。接下来,暂且不对Global“做文章”,只对Action1做文章。那么,操作步骤正式开始。   Step 1,选中“Action1”这个Sheet,将鼠标光标移动至Column“A”,然后双击,如图1-198所示。 图1-198   Step 2,待弹出Change Parameter Name窗口后,任意输入一个name值,这个值将会变成A列的列名。在这里,作者输入了“演示”这两个字,最后点击OK,如图1-199(注:本步骤可以省略,这样的话,列名等于A)。 图1-199   Step 3,到此为止,已经完成了一大半的操作,已经为测试数据建好了一个“家”,以后它们完全可以搬到这个位置“居住”。接下来,在对应“演示”这列的第一行交叉处写入一个值,这个值就是测试数据,如图1-200所示。 图1-200 如图1-200所示,测试数据部分的工作已经完成了,最后一步就是如何在脚本中引用了。在此,介绍第一个Data Table语法—“引用单元格”,如下: DataTable.Value (ParameterID , SheetID) DataTable (ParameterID , SheetID)   语法分析。   Data Table本身就是一个Object,它可以点出一个Value方法,然后就可以通过在后面括号中设置参数来定位到单元格的值了。参数一共有两个,第一个参数“ParameterID”指代列名(在这个实例中列名是“演示”),第二个参数“SheetID”则指代Sheet的名字。   注:在这里,Value方法可以省略,效果也是一样的。   Step 4,最后一步,完成以下脚本成功引用到单元格,使得百度搜索框可以输入单元格中的测试数据,脚本如下所示: Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set DataTable.Value("演示","Action1")   小提示:当DataTable的列数超过2列时,在输入“DataTable”(以后会自动出现代码提示,列出当前Sheet下的所有列的列名。DataTable.Value不适用),如图1-201和图1-202所示。 图1-201 图1-202   小结:   这是一个最基本的例子,只有学会了这个基本实例,读者才能继续后面的学习,后面的一些内容是万变不离其宗的,核心的东西是一致的。 1.8.2.3  Global Sheet  VS  Local Sheet   如果有这样一个测试需求—进行3次百度搜索的业务流程,但是每次输入的关键字必须不一样,此时QTP该怎么完成?请看参考答案,见如下脚本: '打开网页 -- 第1次 SystemUtil.Run "www.baidu.com" '输入关键字“test1”并点击搜索 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set "test1" Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click '关闭网页 Browser("百度一下,你就知道").Close '相同代码 -- 第2次 SystemUtil.Run "www.baidu.com" '只变更测试数据 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set "test2" Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click Browser("百度一下,你就知道").Close '相同代码 --第3次 SystemUtil.Run "www.baidu.com" '只变更测试数据 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set "test3" Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click Browser("百度一下,你就知道").Close   正确答案已经公布了,运行成功,自动化测试脚本成功实现!但是,这就是我们想要的吗?但是如果要输入10次不同的测试数据呢?甚至于上百次呢?难道也要跟着复制→粘贴100次吗?那这脚本该有多庞大啊!   有什么更好的解决方法吗?答案是肯定的,那就是接下来要介绍的Global Sheet。在上一个小节中,所讲的实例是用Local Sheet(Action1)来完成的,那么接下来就来看下面这段代码,看看用Global Sheet是否能够圆满完成任务,脚本如下所示: '打开百度首页 SystemUtil.Run "www.baidu.com" '将DataTable里的值传递给一个变量 testData = DataTable.Value("关键字输入","Global") '使用该变量,并将其填入关键字输入框 Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("关键字输入框").Set testData Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebButton("百度一下").Click '关闭网页 Browser("百度一下,你就知道").Close   该脚本仅用了几行代码就能完成3次业务循环吗?而且还要用不同的数据?别着急,我们来运行一下该脚本,看一下运行结果,是否自动化测试圆满完成了,如图1-203所示。 图1-203   图1-203是QTP的另一个重要模块“测试报告(Test Results)”,这个在后面的章节会详细介绍,这里读者只需要看运行结果就行了。我们可以看到,QTP运行了3次迭代,这就证明了QTP的确进行了3次业务循环。那接下来,再来证明这3次业务循环所用的测试数据都是完全不同的,如图1-204所示。 图1-204 如图1-204所示,点击Run-Time Data Table以后,可以看到脚本在运行过程中所使用的所有数据记录,分别是test1、test2、test3。到此,大家应该没有任何疑问了吧?的确是运行了3次业务,切实地输入了各不相同的测试数据。想知道这些测试数据哪里来的吗?答案现在正式揭晓,如图1-205所示。 图1-205   有没有似曾相识的感觉?在上一个小节中的实例就已经和它打过交道了,它就是QTP的Data Table。请大家注意当前脚本使用的是Global Sheet而不是Local Sheet!在“关键字输入”这一列中输入了3行测试数据,这就是实际运行时输入的数据。   测试数据是怎么来的,我们已经搞清楚了。但是相信读者现在一定还有另外一个疑问,那就是QTP为什么会执行3次,脚本中并没有做任何循环!这就要引出本小节内容的第一个知识点了。   1.Global Sheet是一个全局变量!有几行数据,程序就要回放几次   所以,这也就很清楚地解释了为什么QTP运行了3次,就是因为当前的Global Sheet中有3行测试数据。第1次执行使用第一行数据test1,第二次执行使用第二行数据test2,第三次执行使用第三行数据test3,依此可以一直类推下去。   现在设置了3行测试数据,这些数据不一定是我们每次都需要的,那该怎么办?删除它们?等需要了再添加?那多麻烦。QTP提供了一个很有用的功能,那就是Data Table iterations设置,先来认识一下它,调用它的方法如下。   QTP上方菜单栏→File→Settings→Run打开后的结果如图1-206所示。 图1-206   如图1-206所示,这就是Data Table iterations设置界面,默认选中的是Run on all rows(图中标记2),即DataTable中有几行数据就运行几次,刚才的实例根据业务要求设置了3行数据,所以它会去运行3次。在很多情况下,自动化测试数据只需要1个,那么此时就可以选中Run one iteration only这个选项(图中标记1),这样就完全可以不必去删除本次不会用到的业务数据了。最后一个选项(图中标记3)叫Run from row X to row Y,这个也非常好理解,就是设置一个范围,QTP就会根据设定的范围进行迭代运行,当然,千万不要将范围超出最大的范围(在本实例中最大范围是3)。到此,就要引出另一个知识点了。   2.Global Sheet这个全局变量是受Data Table iterations控制的   那么,读者要问了:“现在Global Sheet和Local Sheet都通过实例讲解过了,那它们之间有什么区别呢?”让我们再次引出另一个知识点。   3.Local Sheet是个局部变量,它并不受Data Table iterations控制,无论有多少行数据,它只运行一次(前提是Global Sheet没有数据,或只有一行数据,或设置为只运行一次)   让我们来看一个实例,把Global Sheet的数据都清空,然后在Local Sheet中建立3行测试数据,如图1-207和图1-208所示。 图1-207 与1-208 然后,仍然复用之前的脚本,唯一更改的一处就是把引用Globl Sheet改成引用Local Sheet,代码如下所示: SystemUtil.Run "www.baidu.com" '唯一的区别就是把Global改成了Action1 testData = DataTable.Value("关键字输入","Action1") Browser("百度一下,你就知道").Page("百度一下,你就知道").WebEdit("关键字输入框").Set testData Browser("百度一下,你就知道").Page("百度一下,你就知道").WebButton("百度一下").Click Browser("百度一下,你就知道").Close   然后,运行脚本,来看一下尽管Local Sheet中有3行测试数据,但是是否只运行了一次,如图1-209所示。 图1-209   很明显,图1-209已经给出了结果!通过这两个实例,已经明确了它们之间各自的用处。但是呢,Data Table的确是QTP的一个难点,即使Global Sheet没有数据,仍然可以通过别的方式(代码控制的方式排除在外)去执行Local Sheet下的所有行。在Test Flow(后续章节会介绍)里反击Action后可以进入Action的一些相关设置,如图1-210和图1-211所示。 图1-210 图1-211   如图1-211,界面很熟悉吧?它和Global的Data Table iterations设置界面相似,在这里选中Run on all rows就可以执行Local Sheet下的所有测试数据了。虽然都是执行所有行数据,不过它们的区别还是很大的,先看一下执行结果,如图1-212(脚本略)。 图1-212   光看一张图可不行,绝对不够直观,再回过头看之前Global的那张图,如图1-213所示。 图1-213   这样很直观了吧?区别相当明显,Local Sheet执行了3次自身Action的迭代,而Global Sheet就相当于执行了3次脚本,这之间可是有着天壤之别的!至于这天囊之别的细节之处不是一本书能够细致介绍的,需要读者亲自实践、细细品味,结合自己的项目多思考。   在最后,还总结了一些Global和Local之间的逻辑规则,大致为以下几点。   ● Global不止一行数据(设置为Run on all rows,以下都是……),Action也设置为Run on all rows(以下都是……)且假设双方都具有3行测试数据,此时Global和Action的每行都要运行且同步运行(即Global在取第2行数据时,Action也对应地取它的第二行数据)。   ● 当Global不止一行数据且Global的行数大于Action的行数,那么当Action执行到最后一行后,Global以后所执行的行数,Action都用它的最后一行数据去补,比如Action的最后一行测试数据是test3,那么即使Global执行到了100行,也一直对应的是Action中test3的这一行数据。   ● 引用上面的条件,如果Global的行数小于Action的行数的话,Action就执行不到最后一行了。 2.1  VBScript在项目中的应用   阶段要点   ● Option Explicit的使用。   ● VBScript基础知识解析。   ● 10个在实际项目中常用的VBS函数。   ● 类在VBS中的应用。   ● 扩展内容:VBS的SendKeys方法。 2.1.1  请培养代码规范的好习惯   从第2章开始,读者要学习或者说积累的是更多实际项目中的经验和遇到各种项目时的不同的解决思路。由于QTP的脚本语言是基于VBS的,因此VBS对于学习自动化测试还是起到了相当大的作用,VBS作为QTP的官方脚本语言,则是我们在做项目时必不可免的要经常使用的。所以,作者将VBS的学习放在第2章的第1章节,旨在打开从QTP使用到利用QTP做实际项目的这扇大门。   首先,认识一下VBS,它又称VBScript,是Visual Basic Script的简称,即Visual Basic脚本语言。VBScript可以通过Windows脚本宿主调用COM,因而可以使用Windows操作系统中可以被使用的程序库,如可以使用Microsoft Office的库、WSH、AOM等。当然它也可以使用其他程序和操作系统本身的库。因此,学习VBScript对于测试人员来说非常重要。通常当学习一门编程语言时,第一句代码往往是变量申明,VBScript也是一样,一起来看一个最简单的实例。   1.定义变量—Dim   例如: Dim helloworld '定义变量 helloworld = "QTP自动化测试技术领航" '给变量进行赋值 msgbox helloworld '弹出消息框显示变量   复制以上代码,在QTP中或另外保存为helloworld.vbs后直接运行,运行结果如图2-1所示。 图2-1   由于VBScript语法不是很严谨,因此,可以不用申明变量就可以直接使用,例如: helloworld = "QTP自动化测试技术领航" '给变量进行赋值 msgbox helloworld '弹出消息框显示变量   这样就可以省去很多申明变量的时间,增加代码开发的速度,但是这样会有一个问题,来看以下这个脚本: helloworld = "QTP自动化测试技术领航" '给变量进行赋值 msgbox helloword '弹出消息框显示变量   保存以上脚本后运行,会发现弹出框并没有任何数据,而是一个空值,如图2-2所示。 图2-2   为什么会这样?因为在这里输入的helloworld被拼写成了helloword,少了一个小写“L”,因此才导致打印出来一个空值。当我们在申明很多变量的时候很容易发生类似这种错误,因此,这里就要给代码中加上显示申明,这样才不会出现上述的这种情况,下面就来看一下具体怎么使用。   2.显示申明—Option Explicit  强制所有变量必须先申明才能使用   例如: Option Explicit '显示申明变量 Dim helloworld '定义变量 helloworld = "QTP自动化测试技术领航" '给变量进行赋值 msgbox helloword '弹出消息框显示变量   运行以上代码就可以直接定位问题,出现错误提示“变量未定义”,如图2-3所示。 图2-3   很多读者在写VBScript时,不喜欢使用显示申明。其实显示申明能够检查你的程序,建议大家能够养成这个好习惯。否则在大量的变量面前一定会束手无策,简单总结一下它的几个优点。   ● 显示申明是脚本编写人员的一种好习惯。   ● 可以防止很多不必要的错误发生,大型项目更加明显。   ● 减少资源占用。   ● 代码提示的优势 ,如图2-4所示。 图2-4   总结:作者在此只是举了一个Option Explicit的例子,其实在编写代码的时候,处处都应该注意代码规范和树立良好的习惯,比如多做一些注释等,这样对以后的脚本维护是有很大的好处的。 2.1.2  VBS基础知识提炼 2.1.2.1  VBS的基本语法   1.变量   (1)所有单引号后面的内容都被解释为注释。   (2)在VBScript中,变量的命名规则遵循标准的命名规则,需要注意的是:在VBScript中对变量、方法、函数和对象的引用是不区分大小写的。在申明变量时,要显式地申明一个变量,需要使用关键字Dim来告诉VBScript创建一个变量,并将变量名称跟在其后。申明多个同类型变量,可以用逗号分隔。   注意:VBScript中不允许在申明变量的时候同时给变量赋值。但是允许在一行代码内同时对两个变量进行赋值,中间用冒号分隔。   (3)VBScript在定义时只有一种变量类型,在实际使用中需要使用类型转换函数来将变量转换成相应的变量类型。   Cbool函数将变量转换成布尔值。   Cbyte函数将变量转换为0~255的整数。   Ccur函数、Cdbl函数和Csng函数将变量转换为浮点数值,前者只精确到小数点后4位,后两者要更加精确,数值的范围也要大的多。   Cdate函数将变量转换为日期值。   Cint函数和Clng函数将变量转换为整数,后者的范围比前者要大的多。   Cstr函数将变量转换为字符串。   2.数组   数组的定义与变量非常类似,只需要在变量后描述这个数组的个数和维数。需要注意的是:数组在“定义”时下标是从1开始的,而在“访问”时下标总是从0开始,以数组定义中数值减一结束。也就是说如果要定义一个有十个数据的数组,将这样书写代码:Dim array(10),同样,当你要访问第五个元素时,实际的代码是array(4)。   当然,可以通过不指定数组的个数和维数来申明动态数组。等到数组的个数和维数固定后,使用关键字ReDim来改变数组。   注意,在改变数组的大小时,数组的数据会被破坏,使用关键字preserve来保护数据。例如:   ReDim“空格”preserve“空格”array“括号”个数“逗号”维数“括号”: ReDim preserve array (x , y )   3.操作符   在VBScript运算符中,加减乘除都是常用的符号,乘方使用的是?^?,取模使用的是Mod。   在比较操作符中,等于、小于、大于、小于等于(<=)、大于等于(>=)都与常用的符号是一致的,而不等于是小于和大于连用(<>)。   逻辑运算符为:“和”操作→AND,“非”操作→NOT,“或”操作→OR。   也可以使用操作符  +  和操作符&来连接字符串,一般使用&操作符;另外还有一个比较特殊的操作符Is用来比较对象,例如。按钮对象,如果对象是同一类型,结果就是真,如果对象不是同一类型,结果就是假。   4.条件语句主要有If……Then语句和Select Case语句两种形式   在If……Then语句中,其基本形式为: If 条件 Then 处理条件的语句 End If   基本形式只能对单个条件进行验证,如果有两个条件,则需要在基本形式中添加单行语句Else,如果还有更多的条件需要验证,则需要添加语句:ElseIf条件Then以及Else,如下: If 条件 Then 处理条件的语句 ElseIf 条件 Then 处理条件的语句 Else 处理条件的语句 End If   在Select Case语句中,其基本形式为: Select Case 变量 Case 条件值 处理条件语句 Case 条件值 处理条件语句 Case Else 处理条件语句 End Select   注意:在执行字符串比较时,需要特别注意大小写,一般情况下,在比较前,使用lCase函数将字符串转换成小写,使用uCase函数将字符串转换成大写。   5.循环控制语句   循环控制语句共分For……Next循环、For……Each循环、Do……While循环、Do……Until循环、While循环5种形式。   在使用循环控制语句前,首先要对循环条件进行判断,如果循环次数是有固定次数的,那么使用For……Next循环,其结构为: For 计数器变量 = 开始计数值 To 最后计数值 执行循环体 Next   如果是需要对数组或对象集合中的每一个元素进行判断,则需要使用For……Each循环,其结构为: For Each 循环计数变量 In 要查看的对象或数组 执行处理语句 Next   注:在上述两种循环中随时可以使用Exit For来退出循环。   如果希望在条件满足时执行一段代码则使用Do……While语句,结构为: Do While 条件 执行循环体 Loop   如果希望在条件不满足时执行代码,则使用Do……Until语句,结构为: Do Until 条件 执行循环体 Loop   注:在这两种循环语句中,是使用Exit Do来退出循环的。   最后一种循环语句是条件满足时一直执行循环,如下: While 条件 执行循环体 Wend 2.1.2.2  错误处理   引发错误的原因有很多,例如,用户输入了错误类型的值,或者脚本找不到必需的文件或者目录等,可以使用循环技术来处理错误,但是VBS本身也提供了一些基本技术来进行错误的检测和处理。   (1)最常见的错误是运行时错误,也就是说错误在脚本正在运行的时候发生,是脚本试图进行非法操作的结果,例如,0被当作为除数。在VBS中,任何运行时错误都是严重的,此时,脚本将停止运行,并在屏幕上显示一个错误消息。可以在脚本的开头添加On Error Resume Next这行语句,它可以告诉VBS在运行时跳过发生错误的语句,紧接着执行跟在它后面的语句。这样,一旦发生错误时,该语句将会把相关的错误号、错误描述和相关源代码压入错误堆栈。   (2)虽然On  Error  Resume  Next语句可以防止VBS脚本在发生错误时停止运行,但是它并不能真正处理错误,要处理错误,需要在脚本中增加一些语句,用来检查错误条件,并在错误发生时处理它。   VBScript恰好提供了一个对象“err对象”,它有两个方法Clear、Raise,5个属性:description、helpcontext、helpfile、number、source、err对象不用引用实例,可以直接使用,例如: On Error Resume Next a = 5 b = 0 c = a / b If err.number <> 0 Then WScript.echo err.number &"/"& err.description &"/"& err.source End If   执行一下该脚本,VBS会详细地报出错误信息,如图2-5所示。 图2-5 2.1.3  常用函数解析   Date  函数   作用:返回当前系统日期。   语法:Data。   参数:无。   示例: Dim MyDate MyDate = Date Msgbox MyDate   使用QTP执行以后的结果如图2-6所示。 图2-6 -函数实际项目臆测&点评-   经常需要在自动化测试过程中写一些的测试执行Log,那么抓取时间的函数就相当有必要了,也可以用于错误截图时的后缀名。   Now  函数   作用:返回当前系统时间。   语法:Now。   参数:无。   示例: Dim MyNow MyNow = Now Msgbox MyNow   使用QTP执行以后的结果如图2-7所示。 图2-7 -函数实际项目臆测&点评-   如果说写Log文件,更加推荐本函数,因为有具体的时间而不仅仅只有日期。如果作为错误截图,个人认为本函数相对就比较不合适一些!毕竟我们都知道图片的文件名是不能有“:”这种字符的,即使最终去除了这些特殊字符,但是整体的表现力远不如前者,同时也会显得很繁琐!   DateDiff  函数   作用:返回两个日期之间的间隔。   语法:DateDiff (interval, date1, date2)。   参数。interval:通俗地讲它就是事先设一个时间比较单位,这个单位可以且只可以是表2-1中的单位。   表2-1                        时间比较单位 Setting Description Yyyy Year Q Quarter M Month Y Day of year D Day W Weekday Ww Week of year h Hour n Minute S Second   注:参数只能设置成上表中左边这一栏的数据。  date1:比较数据1。   date2:比较数据2。   示例: Dim tDateDiff,date1,date2 date1 = Now date2 = Date tDateDiff = DateDiff("d",date1,date2) Msgbox tDateDiff   使用QTP执行以后的结果如图2-8所示。 图2-8   注:返回0是正确的,因为两个Date之间的天数差异是0天,读者可以自行尝试其他参数。 -函数实际项目臆测&点评-   作者很兴奋地告诉读者,使用好这个函数,QTP一样能做一些简单的性能测试工作!毕竟,这个函数可以对比两个事务间的时间间距,而且可以精确到以秒为单位!   Left  函数   作用:返回字符串最左边的指定数量的字符。   语法:Left (string, length)。   参数。   string:字符串表达式,其最左边的字符被返回。如果string参数中包含Null,则返回Null。   length:数值表达式,指明要返回的字符数目。如果是0,返回零长度字符串("");如果大于或等于string参数中的字符总数,则返回整个字符串。   示例: Dim MyString, LeftString MyString = "VBScript" LeftString = Left (MyString, 3) ‘结果:最终应返回VBS -函数实际项目臆测&点评-   如果读者需要某个字符串的指定几个字符时,就是使用该函数的时机!   Right  函数   作用:返回字符串最右边的指定数量的字符。   语法:Right(string, length)。   参数。   string:字符串表达式,其最右边的字符被返回。如果string参数中包含Null,则返回Null。   length:数值表达式,指明要返回的字符数目。如果是0,返回零长度字符串("");如果大于或等于string参数中的字符总数,则返回整个字符串。   示例: Dim MyString, RightString MyString = "VBSCript" RightString = Right (MyString, 6) ‘结果:最终应返回SCript -函数实际项目臆测&点评-   区别在于一个从左到右,一个从右到左!   Len  函数   作用:返回字符串内字符的数目。   语法:Len(string)。   参数。   string:任意有效的字符串表达式。如果string参数包含Null,则返回Null。   示例: Dim MyString MyString = Len (“VBScript”) ‘最终应返回8 -函数实际项目臆测&点评-   往往用于对比两个字符串时的一些特殊业务需求,有时也可以用于代码调试。   Mid  函数   作用:从字符串中返回指定数目的字符。   语法:Mid(string, start[, length])。   参数。   string:字符串表达式,从中返回字符。如果string包含Null,则返回Null。   start:string中被提取的字符部分的开始位置。如果start超过了string中字符的数目,Mid将返回零长度字符串("")。   length:要返回的字符数。如果省略或length超过文本的字符数(包括start处的字符),将返回字符串中从start到字符串结束的所有字符。   示例: Dim MyVar MyVar = Mid ("VB script is fun!", 4, 6) ‘结果:最终应返回script -函数实际项目臆测&点评-   比Left和Right智能,它可以在任意位置获取,但是函数本身比较繁琐,建议只在Left和Right函数不能胜任时使用。   Split  函数   作用。   在指定的分隔符参数出现的所有位置断开string对象,将其拆分为子字符串,然后以数组形式返回子字符串。   语法:Split (expression[, delimiter[, count[, compare]]])。   参数。   expression:主体字符串,也就是要被拆分处的字符或字符串。   delimiter:拆分元素,默认是("?")。   count:Number要放入数组中的项目数(可选)。   compare:0是二进制比较,1是文本比较。0为缺省值。   示例: Dim MyString, MyArray, Msg MyString = "VBscriptXisXfun!" MyArray = Split (MyString, "x", -1, 1) ‘返回结果: ' MyArray(0) = "VBscrīpt". ' MyArray(1) = "is". ' MyArray(2) = "fun!". Msg = MyArray(0) & " " & MyArray(1) Msg = Msg & " " & MyArray(2) MsgBox Msg  使用QTP执行以后的结果如图2-9所示。 图2-9 -函数实际项目臆测&点评-   在测试中经常获取到一个关键字符串后想去使用它,比如将获取到的一个关键信息写入日志,但如果该信息过于冗长或者有些字符串并不想使用它,完全可以使用Split函数来解决这类问题。   LTrim、RTrim与Trim  函数   作用:返回不带前导空格(LTrim)、后续空格(RTrim)或所有空格(Trim)的字符串副本。   语法:   LTrim(string)。   RTrim(string)。   Trim(string)。   参数。   string:任意有效的字符串表达式。如果string参数中包含Null,则返回Null。   示例。 Dim MyVar MyVar = LTrim (" vbscript ") 'MyVar 包含 "vbscript " MyVar = RTrim (" vbscript ") 'MyVar 包含 " vbscript" MyVar = Trim (" vbscript ") 'MyVar 包含 "vbscript" -函数实际项目臆测&点评-   假设我们要获取一个元素在页面上的值,然后使用这个值做一些检查和验证的判断。但是经常会遇到这样一个困扰,打个比方,我们从观察的角度上看(通过Msgbox的方法等),取到的值的确是“qtp”,然后写如下这段很简单的判断代码: If a = "qtp" Then Reporter.ReportEvent micPass,"检查变量a的值","等于qtp" Else Reporter.ReportEvent micFail,"检查变量a的值","等于qtp" End If   但是最终验证失败了,为什么?相信软件测试中遇到过很多次类似的问题,其实QTP取下来的值多了一个空格,所以导致最后判断失败。遇到类似的情况,就完全可以用Trim来解决问题。   Replace  函数   作用:返回字符串,其中指定数目的某子字符串被替换为另一个子字符串。   语法: Replace (expression, find, replacewith[, compare[, count[, start]]])。   参数。   expression:[必选项]字符串表达式包含要替代的子字符串。   find:[必选项]被搜索的子字符串。   replacewith:[必选项]用于替换的子字符串。   start:[可选项]expression中开始搜索子字符串的位置。如果省略,默认值为1。在和count关联时必须用。   count:[可选项]执行子字符串替换的数目。如果省略,默认值为-1,表示进行所有可能的替换。在和start关联时必须用。   compare:[可选项]在计算子字符串时使用的比较类型的数值。有关数值如表2-2所示。如果省略,缺省值为0,这意味着必须进行二进制比较。  表2-2                           有关数值 常 数 值 描 述 vbBinaryCompare 0 执行二进制比较 vbTextCompare 1 执行文本比较   Replace返回值如表2-3所示   表2-3                    Replace返回以下值 If Replace返回 expression为零长度 零长度字符串("") expression为Null 错误 find 为零长度 expression的副本 replacewith为零长度 expression的副本,其中删除了所有由find参数指定的内容 start > Len(expression) 零长度字符串 count为0 expression的副本   注:Replace函数的返回值是经过替换(从由start指定的位置开始到expression字符串的结尾)后的字符串,而不是原始字符串从开始至结尾的副本。   示例: Dim MyString MyString = Replace ("XXpXXPXXp", "p", "Y") '二进制比较从字符串左端开始。返回 "XXYXXPXXY"。 -函数实际项目臆测&点评-   部分特殊需求时使用。 2.1.4  Function VS Sub终极角逐   本小节就来介绍下VBS中函数的应用,并且此应用在平时编写公共函数库时经常会被使用到。下面介绍一下VBS中的两个函数。   (1)Sub过程:Sub其实就是一个过程复用,没有返回值。   详解:Sub过程是包含在Sub和End Sub语句之间的一组VBScript语句,执行操作但不返回值。Sub过程可以使用参数(由调用过程传递的常数、变量或表达式)。如果Sub过程无任何参数,则Sub语句必须包含空括号()。   实例: Call QtpSub ‘调用Sub Sub QtpSub Msgbox "我是Sub" End Sub   (2)Function  函数:Function是一个经常会用到的函数,可以有返回值,也有参数。   详解:Function过程是包含在Function和End Function语句之间的一组VBScript语句。Function过程与Sub过程类似,但是Function过程可以返回值。Function过程可以使用参数(由调用过程传递的常数、变量或表达式)。如果Function过程无任何参数,则Function语句必须包含空括号()。Function过程通过函数名返回一个值,这个值是在过程的语句中赋给函数名的。Function返回值的数据类型总是Variant。   实例: Dim myName myName = QtpFunction("QTP自动化测试技术领航") '调用返回函数给变量 Msgbox myName Function QtpFunction (tName) QtpFunction = tName '返回函数 End Function   运行结果如图2-10所示。 图2-10   注意:在QTP中调用函数可以使用Call也可以不使用Call,但是一旦使用Call就需要有返回值,即需要给参数的外面增加一对括号。 2.1.5  获取对象引用GetRef方法   在本小节中,为读者简单介绍一下VBS中的函数指针,我们都知道函数指针是程序算法的一部分,它和数组一样也需要占用一部分的存储空间,也都有相应的地址。不但可以使用指针变量指向数组的首地址,同样也可以使用指针指向函数代码的首地址,我们把指向函数代码首地址的指针变量称为函数指针。   GetRef:它所提供的功能被称为函数指针,即它指向了在指定事件发生时要执行的过程的地址。   实例: '变量强制申明 Option Explicit '定义变量 Dim new_helloworld '定义helloworld函数 Function helloworld (content) MsgBox content End Function '使变量new_helloworld指向helloworld函数 Set new_helloworld = GetRef("helloworld") ‘调用new_helloworld Call new_helloworld ("QTP自动化测试技术领航")   上面这段代码是把变量new_helloworld的指针指向helloworld函数,这样new_helloworld就具有了helloworld函数的功能,放到QTP中执行以后的结果如图2-11所示。 图2-11   如图2-11所示,指针成功指向了helloworld函数,并最终成功调用了MsgBox方法。 2.1.6  类的简单应用   “类”这个名词应该是不陌生的,但是很多自动化测试新人往往不知道,其实在VBScript中一样可以使用Class“类”!而且Class“类”在自动化测试中相当常用,对于代码量增大时,类的结构化就充分体现出了它强大的优势,下面就来看一下类的组成部分以及它们的一些用法。   1.初始化与终结化的应用 Class User Private Sub Class_Initialize '当这个类被创建时执行 End Sub Private Sub Class_Terminate '当类被销毁时执行 End Sub End Class   2.Get与Set的应用 Class User '************定义变量名************* Private s_name Private s_age '************定义Get方法************ Public Property Get name name = s_name End Property Public Property Get age age = s_age End Property '************定义Set方法************ Public Property Let name (new_name) s_name = new_name End Property Public Property Let age (new_age) s_age = new_age End Property End Class Set user1 = New user user1.age = "100" user1.name = "拉生" MsgBox "姓名:" +user1.name+ " 年龄:" +user1.age  把以上这段代码放入QTP执行,结果如图2-12所示。 图2-12   3.函数的应用 Class User Sub msgNow MsgBox now End Sub Function msgContent(content) MsgBox content End Function End Class Set user1 = New user user1.msgContent "当前何时?" user1.msgNow   把以上这段代码放入QTP中执行,结果如图2-14和图2-13所示。 图2-13 图2-14   总结:   虽然Class很实用,但可惜的是在QTP中,目前还不支持Class的编辑,即无法“点”出Class。因此不得不通过其他工具编辑完毕以后再放入QTP中。 2.1.7  VBS中SendKeys与项目结合的妙用   这个小节主要是介绍一下如何使用VBS中的SendKeys命令(这个命令的作用就是模拟键盘操作,将一个或多个按键指令发送到指定Windows窗口来控制应用程序运行),巧妙地使用它可以极大地方便我们的常用操作,其使用格式为。 object.SendKeys string   其中,“object”表示WshShell对象;“string”表示要发送的按键指令字符串,需要放在英文双引号中,而在按键指令字符串中通常又把它分为两类。   1.基本键。   一般来说,要发送的按键指令都可以直接用该按键字符本身来表示,例如,要发送字母“q”,使用“WshShell.SendKeys "q"”即可。当然,也可直接发送多个按键指令,只需要将按键字符按顺序排列在一起即可,例如,要发送按键“qtp”,则可以使用“WshShell.SendKeys "qtp"”来表示。   2.特殊功能键。   对于需要与Shift、Ctrl、Alt?3个控制键组合的按键,SendKeys使用特殊字符来表示。   ● Shift:WshShell.SendKeys "+"。   ● Ctrl:WshShell.SendKeys "^"。   ● Alt:WshShell.SendKeys "%"。   由于“+”、“^”这些字符用来表示特殊的控制按键了,那又如何表示这些按键呢? 很简单,只要用大括号括住这些字符即可,例如。   要发送加号“+”,可使用“WshShell.SendKeys "{+}"”   另外,对于一些不会生成字符的控制功能按键,也同样需要使用大括号,把按键的名称“括起来”,例如,要发送回车键,需要用“WshShell.SendKeys "{ENTER}"”表示,发送向下的方向键用“WshShell.SendKeys "{DOWN}"”表示,其他的比如。   ● Space:WshShell.SendKeys " "。   ● ←:WshShell.SendKeys "{RIGHT}"。   ● ↑:WshShell.SendKeys "{UP}"。   ● F1:WshShell.SendKeys "{F1}"。   注意:如果需要发送多个重复的单字母按键,不必重复输入该字母,SendKeys允许使用简化格式进行描述,使用格式为“{按键  数字}”。例如,要发送10个字母“q”,则输入“WshShell.SendKeys "{q 10}"”即可。   大致的语法都已经了解了,其实其他的按键都是遵循这些法则的,接下来让我们来看看一些使用Sendkeys的实例。   实例一:按F5键刷新桌面: Dim WshShell , Path , i Set WshShell = WScript.CreateObject("WScript.Shell") WshShell.SendKeys "{F5}"   实例二:自动重启电脑: Set WshShell = CreateObject("WScript.Shell") WshShell.SendKeys "^{ESC}u" WshShell.SendKeys "R"   实例三:启动任务管理器: Set WshShell = CreateObject("WScript.Shell") WshShell.SendKeys "^+{ESC}"   实例四:在记事本中输入“Happy Birthday!”字样并保存为birth.txt: Dim WshShell Set WshShell = WScript.CreateObject("WScript.Shell") WshShell.Run "notepad" WScript.Sleep 1500 ‘wait 1.5 second WshShell.AppActivate "无标题 - 记事本" WshShell.SendKeys "H" WScript.Sleep 500 WshShell.SendKeys "a" WScript.Sleep 500 WshShell.SendKeys "p" WScript.Sleep 500 WshShell.SendKeys "p" WScript.Sleep 500 WshShell.SendKeys "y" WScript.Sleep 500 WshShell.SendKeys " " WScript.Sleep 500 WshShell.SendKeys "B" WScript.Sleep 500 WshShell.SendKeys "i" WScript.Sleep 500 WshShell.SendKeys "r" WScript.Sleep 500 WshShell.SendKeys "t" WScript.Sleep 500 WshShell.SendKeys "h" WScript.Sleep 500 WshShell.SendKeys "d" WScript.Sleep 500 WshShell.SendKeys "a" WScript.Sleep 500 WshShell.SendKeys "y" WScript.Sleep 500 WshShell.SendKeys "!" WScript.Sleep 500 WshShell.SendKeys "%FS" WScript.Sleep 500 WshShell.SendKeys "b" WScript.Sleep 500 WshShell.SendKeys "i" WScript.Sleep 500 WshShell.SendKeys "r" WScript.Sleep 500 WshShell.SendKeys "t" WScript.Sleep 500 WshShell.SendKeys "h" WScript.Sleep 500 WshShell.SendKeys "%S" WScript.Sleep 500 WshShell.SendKeys "%FX"   实例五:使计算机死机(请勿随意尝试!)。 Dim WSHSHELL Set WSHSHELL = WScript.CreateObject("WScript.Shell") WSHSHELL.Run " " Wait 1 WSHSHELL.SendKeys "{ENTER}" Wait 1 WSHSHELL.SendKeys "{ENTER}" Wait 1 WSHSHELL.SendKeys "{ENTER}" Wait 1 WSHSHELL.SendKeys "{ENTER}" Wait 1 WSHSHELL.SendKeys "{ENTER}"   实例六:定时关机。 Dim WshShell Set WshShell = WScript.CreateObject("WScript.Shell") WScript.Sleep 2000 WshShell.Run "shutdown -r -t 120" wscript.sleep 6000 WshShell.Run "shutdown –a   注意:在“实例四”中的“wscript.sleep 500”意味着等待0.5秒(1000 = 1秒),而在“实例五”中的“Wait 1”直接意味着等待1秒。另外QTP并不支持前者,所以,使用QTP执行的话只能使用Wait函数,否则会报错。 2.1.8  总结   VBScript的知识点很多,在本书中只是总结一些和QTP息息相关的知识点,以及一些在项目中会使用到的小技巧(比如SendKeys),本章节的内容并不是全面的VBS教学,内容并不是特别系统,但是作者已经讲了VBScript脚本语言和QTP交互的重点部分。   完全可以通过很多网络资源学习VBScript,在本书的后续章节(比如介绍EOM的那一章)也会使用到不少VBScript的知识。最后再通过不断地实践,相信过不了多久就可以随心所欲地应付VBS了,个人认为“VBScript脚本语言”没有必要特意去“学习”!   知识点巩固和举一反三练习   一、利用VBScript中的SendKeys方法写一段代码,使记事本能自动定时保存。   二、编写一个Function、一个Sub并调用它们。   要求:注意代码规范! 2.5  常用保留对象(Utility Objects)   阶段要点   ● 简单介绍常用保留对象。   ● 隐藏保留对象的探索。   ● 掌握自定义保留对象的编写方式。 2.5.1  常用保留对象介绍   在之前的章节中,曾经介绍过利用DataTable做数据处理,Environment环境变量以及利用Reporter编写自定义报告等,这些对象都是一些QTP自身提供的非常基础的对象,它们都有一个共同的名字,就是保留对象。   保留对象:所谓QTP保留对象就是QTP本身预留的一些可用对象。   通俗些讲就是,当打开QTP时它就已经把这些对象给实例化了,直到关闭QTP后,这些保留对象的实例才会终止。这些保留对象都是QTP事先封装好的常用操作对象,无需像开源软件那样都需要自己来搭建这些对象和类库,一切都是现成的!在保留对象中有些是可见的,有些是隐藏的(需要人工开启),具体如何查看隐藏保留对象会在下一个小节中详细进行讲解。   那么保留对象具体在QTP中如何呈现呢?当打开QTP后,按F7键,弹出了步骤生成器窗口,如图2-89所示。 图2-89   在分类中可以选择保留对象类型,之后就可以看到所有可见的保留对象。每个保留对象都是一个COM组件,都可自行创建,其中很多的保留对象在做实例项目测试的时候都是非常实用的,接下来就为大家介绍几个常用的保留对象。   1.Crypt:加密模块保留对象   实现如下所示: PwStr = Crypt.Encrypt("mercury") Dialog("Login").WinEdit("Agent Name:").Set "mercury" Dialog("Login").WinEdit("Password:").SetSecure PwStr Dialog("Login").WinButton("OK").Click   分析:   利用Crypt保留对象可以把字符串自动转化为加密形式的字样,并且每次动态生成的加密密码都是不一样的,其实,此功能就是Password Encoder,如图2-90所示。 图2-90   如图2-90,圈处即为加密后的动态密码,每次生成结果都是不一样的。Crypt方式其实就是此工具的脚本生成形式。  2.Desktop:桌面保留对象   实现如下所示: Desktop.CaptureBitmap "D:\1.bmp"   分析:   通常此对象用于出错时的截图,使用此对象来截图非常方便,只需给定生成截图的路径即可,这个在前面的章节也用过。   3.MercuryTimes:计时保留对象   实现如下所示: ‘计时开始 MercuryTimers("LoginPage_MainPage").Start '****************************************** ' LOGIN MAINPAGE Wait 2 '****************************************** ‘计时结束 MercuryTimers("LoginPage_MainPage").Stop ‘获取总共耗时时间 Print MercuryTimers("LoginPage_MainPage").ElapsedTime   分析:   MercuryTimes保留对象是一个非常实用的对象,它对于测试应用程序的性能是非常有帮助的,此对象也是在QTP 9.2版本才开始加入的。   4.PathFinder:路径保留对象   实现如下所示: Msgbox PathFinder.Locate("Default.xls")   分析:   PathFinder在QTP中的可见方法只有Locate,其参数为文件名称,其作用主要是获取到参数中的文件的完整绝对路径。   5.RandomNumber:随机数保留对象   实现如下所示: Print RandomNumber.Value (0, 100)   分析:   执行以上脚本会得到一个0~100的随机数。注意,此处包含0和100。由于此处Value是默认属性,因此可以省略。 2.5.2  隐藏保留对象介绍   上一小节已经介绍了一些常用的保留对象,但其实保留对象远远不止这些,QTP本身在步骤生成器中还隐藏了一些保留对象,如RegisterUserFunc、WebUtil、UnRegisterUserFunc等,这些保留对象都是QTP的隐藏模式保留对象。你会发现步骤生成器中根本找不到这些保留对象,但是这些对象确实是真实存在的,比如:   ● WebUtil:此对象在QTP的帮助文档里以及对象浏览器里都是找不到的,可以说是QTP的一个隐藏对象。   我们只需要在专家视图中输入WebUtil再加一个“点”,就会展开许多的方法,图2-91就是WebUtil的所有方法。 图2-91   分析:   虽然这些隐藏保留对象在帮助文档里都没有相应的说明,但可以通过其方法名称来了解某些方法的使用方式。读者一定会感觉好奇,这些隐藏保留对象究竟是从哪里来的呢?其实它们都隐藏在注册表之中,现在就把它们统统显示出来。   (1)运行“regedit”进入注册表,如图2-92所示。 图2-92   (2)依次单击HKCU→Software→Mercury Interacitve→QuickTest Professional→MicTest→ReservedObjects下的所有项,如图2-93所示。 图2-93   分析:   如图2-93所示,可以看到QTP中所有存在的保留对象,不管隐藏的还是非隐藏的保留对象都可以直接找到。其中每一项代表着一个保留对象,在此目录中的每一项下的KEY包含ProgID。ProgID代表着保留对象创建的COM字符串,例如: Set oRegisterFunc = CreateObject(“Mercury.RegisterUserFunc”)   这些保留对象基本上都可以通过以上的方式来创建,不过有些只能在QTP中进行创建。   注意:当在QTP中创建这些对象时,也就意味着自行创建了一个保留对象的实例,而此实例与原来QTP默认实例化的保留对象是没有任何关系的。 2.5.3  自定义保留对象   在本小节中,还要给读者讲解一下什么是自定义保留对象。自定义保留对象需要并满足以下两个条件。   ● 自定义的保留对象必须是COM组件。   ● 需要添加自定义保留对象就必须更改注册表信息。   步骤:   (1)可以先用WSC来创建一个自定义COM组件,并保证可以使用CREATEOBJECT进行调用,如图2-94所示。 图2-94   (2)运行regedit进入注册表并找到路径: HKEY_CURRENT_USER\Software\Mercury Interactive\QuickTest Professional\ MicTest\ReservedObjects\   新建项:Zzxxbb,如图2-95所示。 图2-95   (3)进入此项后。   ● 新建字符串值ProgID,数值为COM组件的程序ID,这里输入zzxxbb.WSC。   ● 新建字符串值UIName,数值为保留对象的名字,这里填写Zzxxbb。   ● 新建DWORD值VisibleMode,数值为显示模式,这里填写表示控制自动完成可见,在此输入2,如图2-96所示。 图2-96  (4)设置完之后就可以打开QTP查看已经自定义好的保留对象。   ● 按F7键显示结果,如图2-97所示。 图2-97   ● Complete Word显示结果,如图2-98所示。 图2-98   ● 智能感知结果如图2-99所示。 图2-99 2.5.4  总结   这一节主要是扩展了一个自定义保留对象的应用知识,它是通过更改注册表,然后对QTP进行注入保留对象实现的。 2.6  QuickTest自动化模型对象(AOM)   阶段要点   ● 帮助了解不同环境下AOM的调用   ● 深入理解QTP自动化模型的引用过程   ● 掌握AOM具体实际用法   ● 了解AOM常见问题与错误解决方法 2.6.1  不同开发环境下的AOM使用解析   AOM是一个可以自动化QTP的自动化对象模型,它可以对QTP进行自动化配置操作,以及QTP的运行回放自动化等。   1.AOM:Automation Object Model(自动化对象模型)   下面就来看一个最简单的创建AOM的例子: Dim qtApp Set qtApp = CreateObject("QuickTest.Application") ' 创建对象 qtApp.Launch 'Start QuickTest qtApp.Visible = True ' 设置为可见   分析:   将以上代码保存为VBS后缀格式的文件后,双击运行即可自动启动QTP应用程序。以上脚本是一个最为简单的创建AOM对象并启动QTP的例子,实现的语言为VBS。作为DLL模型调用,其调用方式还远远不止这些。   2.VB调用方式 Dim qtApp As QuickTest.Application ' 申明AOM对象 Set qtApp = New QuickTest.Application ' 创建AOM对象 qtApp.Launch ' 启动QTP qtApp.Visible = True ' 设置为可见   3.C#调用方式 QuickTest.Application app = New QuickTest.Application(); // 创建QTP对象 app.Launch(); // 启动QTP app.Visible = True; // 让QTP可见   4.JavaScript调用方式 var qtApp = new ActiveXObject("QuickTest.Application"); // 创建AOM对象 qtApp.Launch(); // 启动QTP qtApp.Visible = true // 设置为可见 2.6.2  QTP自动化模型的引用   在上一小节已经简单介绍了QTP自动化模型,以及其在不同环境下的AOM调用方式,那么在此就可以引申出一个问题,为什么那么多语言都能够调用AOM来自动化QTP?这是因为QTP安装程序中已经注册了一个核心DLL文件的COM,这个DLL就是在安装文件夹下的BIN目录下的QTObjectModel.dll。   下面就来简单地完成一个启动QTP→打开脚本→运行回放的这样的一个实例。在这里使用的是VBSEdit工具。   (1)首先在这里引用QTObjectModel.dll。   引用方法。   ● Tools - References - Add。   ● 在Prog ID中输入QuickTest.Application。   ● 在TypeLib path中找到dll的路径,位于安装路径的BIN目录下,如图2-100所示。 图2-100  (2)在引用完dll后就可以创建AOM对象了,并且会有相应的代码提示,如图2-101所示。 图2-101   如图2-101所示,在qtapp后输入“点”,VBSEdit即会自动通过CreateObject后的Prog ID去搜索相应的dll库,一旦搜索到之后,智能提示即会自动打开。   (3)当创建好AOM对象的同时,也可以在对象浏览器中看到这个dll下的所有方法和属性,如图2-102所示。 图2-102   分析:   可以看见图2-102中存在非常多的对象和属性,在这些对象当中相信大家都已经看到了一些熟悉的面孔,如Action、Addins、ObjectIdentification、Resources等。当然,这里的那么多方法和属性,作者也不可能一一列举出来,详细的用法可以参见QuickTest Automation Reference,读者可以利用网络资源下载到这份文档,如图2-103所示,或发送索取请求到作者的邮箱。 图2-103 2.6.3  让QTP舞动起来   前面介绍了那么多理论上的东西,下面就来实践一下,让QTP真正地“动起来”,并自动完成一个“从启动到新建测试到输入脚本到执行到关闭的”完整的自动化测试流程。   ● 实例: '创建对象 Set qtapp = CreateObject("QuickTest.Application") '启动QTP qtapp.Launch '设置为可见 qtapp.Visible = True '新建一个test脚本 qtapp.New '为Action1设置需要执行的脚本字符串 qtapp.Test.Actions("Action1").SetScript "Msgbox 0" '运行测试脚本 qtapp.Test.Run '退出QTP qtapp.Quit '释放 Set qtapp = Nothing   分析:   以上脚本是一个非常基础和简单的调用AOM的经典例子。首先,Launch方法的作用是启动QTP应用;接着New方法的作用则是新建一个测试;SetScript的作用是可以在Action1中自动输入对应的字符串;Run方法为运行测试,注意其中的第二个参数可以控制是否等待Run方法执行完毕;最后使用Quit方法自动退出QTP应用并释放AOM对象。   注意事项:   (1)执行以上代码时,系统会多出一个QTAutomationAgent.exe的进程(如图2-104所示),此进程在运行完以上代码后会自动关闭。但是需要注意的是,当在调试代码或者是做非法终止以上代码的行为而最终又未能使以上代码执行到最后完毕的话,此进程不会自动关闭,这样就会导致第二次创建AOM对象时报错。因此,如果在调试代码时遇到类似“远程过程调用失败”的错误提示,就可以试试去掉QTAutomationAgent.exe这个进程,然后重新调试代码就可以了。 图2-104   (2)如果在QTP直接创建AOM对象是会报错的,因为QTP只允许有一个实例对象,当已经开启了一个QTP之后就不可以再创建另外一个QTP了,因此这个时候可以直接使用GetObject("", "QuickTest.Application") 来对当前启动的QTP进行操作,当添加如图2-105中的脚本后,一旦点击运行,当前QTP就会自动隐藏,运行完后自动恢复可见。 图2-105 2.6.4  总结   本章节主要讲解了多脚本语言环境下调用AOM的不同方式,以及自动化模型的引用过程,这些基础对于以后独立建立测试执行框架有非常大的帮助。在后续的章节中,还会对对象库自动化模型(ORAOM)进行详细介绍。   本章节不设置习题,读者只需会套用模板代码并充分理解即可。 2.7  无人值守测试的守护神—场景恢复(Recovery Scenarios)   阶段要点   ● 场景恢复的误区。   ● 自定义场景恢复函数。 2.7.1  必须知道的几种场景恢复的误区   相信只要是有过自动化测试经验的读者一定会遇到过,在执行测试过程中碰到异常错误的情况,如弹出窗口、应用程序Crash等异常而导致测试执行的停止,这会使情况变得非常糟糕,因为并没有完成预期所需要执行完毕的所有自动化测试用例。正是在这种情况下,场景恢复被迫诞生了!   场景恢复可以应对多种类型的错误并进行恢复操作,在QTP中设置场景恢复还是比较简单的,在每新建一个场景恢复文件时都会有Wizard向导帮助我们去理解如何来设置场景恢复。因此,有关这些内容本书就不着重介绍了,如果有任何问题建议好好查看一下帮助文档。这里主要介绍一下场景恢复的常见误区,很多读者在使用场景恢复时经常会遇到场景恢复不起作用的情况,甚至还有很多读者认为QTP的场景恢复功能有问题,该执行场景恢复时却没有执行。难道这真的是QTP的场景恢复在功能上存在缺陷吗?这是很容易犯的错误。在这里就把这几个常见的误区给罗列一下。   1.测试脚本中含有的对象不在对象库中 ‘此脚本中的测试对象不在对象库中 Browser(“NotInObjectRepository”).Sync   分析:   当脚本中的测试对象不在对象库中时,场景恢复不会被激活,并且会提示在对象库中找不到此对象,如图2-106所示。 图2-106   2.场景恢复设置没有被激活   具体如图2-107所示。 图2-107   分析:   如果在Activate recovery scenarios下拉列表中选择了Never,那么场景恢复无法被激活。此处下拉列表在正常情况下应该选择Error,意味着当遇到符合指定条件的错误下才会被成功激活。  3.VbScript脚本语言自身错误 ‘VBS脚本语言出错 X = 1/0   分析:   类似以上脚本中出现的VBS语言本身的错误或者语法错误都无法激活场景恢复。当执行到此脚本时,程序会报出图2-108中的错误。 图2-108   如图2-108所示,若是需要控制类似的vbs错误,通常会使用On Error Resume Next与Err对象来进行错误控制。   4.当MsgBox切断测试过程时 Dim a a = 1 Msgbox a Browser("micClass:=Browser").Close   分析:   由于MsgBox弹出框自动切断了当前的测试执行链,也就是暂停了测试,但并没有出现测试Error,因此场景恢复不会被激活。   小结:   ● 必须是对象库中已经存在的对象错误才会自动激活;   ● 必须是QTP自身封装对象错误才会自动激活;   ● 对于VBS语言环境错误不会自动激活。 2.7.2  陌生的Web默认场景恢复   可能很多读者不太清楚,在QTP安装完毕之后,在它安装目录下存在这么一个Recovery文件夹,里面存放着测试Web时默认的场景恢复文件。此文件是QTP官方为我们Web自动化测试人员提供的场景恢复文件。   路径:<安装目录>\recovery\WebRecovery.qrs。   此文件是一个总的场景恢复库文件。在Recovery Scenario Manager中可以直接对此文件进行查看,如图2-109所示。 图2-109   分析:   如图2-109所示,这些都是QTP官方提供的场景恢复文件中的所有弹出框场景,建议读者如果是在进行Web自动化测试过程中,可以使用此场景恢复文件与自己设计的场景恢复文件结合进行使用,效果更佳! 2.7.3  无所不能的自定义场景恢复函数   在上一个小节中已经提到过,通常在出现一些异常事件的情况下,如弹出框,被测程序Crash等,可以在QTP中加载相应的场景恢复机制,这样当错误发生的时候,QTP就会自动激活它,进行测试场景的恢复,从而保证测试的继续执行,不至于让脚本卡着一直到超时失败。因此,RS在自动化测试中的地位是相当重要的。   那么在这一小节,介绍一项非常实用的技术:场景恢复的自定义函数RecoveryFunction,这个函数在QTP帮助文档中没有任何的信息来指导我们具体应该怎么做,因此在这里就简明地概述一下。   实例:当出现错误,利用场景恢复自动获取当前出错的测试对象、方法、参数。   1.打开Recovery Scenario Manager,如图2-110所示。 图2-110   2.点击新建一个场景恢复文件,如图2-111所示。 图2-111   3.进入到场景恢复向导,点击下一步,如图2-112所示。 图2-112   4.选择Test run error,如图2-113所示。 图2-113   5.选择Any error,如图2-114所示。 图2-114   6.点击两次下一步后,选择Function call自定义场景恢复函数,如图2-115所示。 图2-115  7.选择一个空的场景恢复函数库文件,并为其定义场景恢复函数,如图2-116所示。 图2-116   8.完成自定义函数后,选择Stop the test run,并把其场景恢复文件加载到当前设置。   分析:   在函数中写入如下代码: Function RecoveryFunction1(Object, Method, Arguments, retVal) Print Object.GetTOProperty("testObjName") Print Method Print join(Arguments,",") Print DescribeResult(Result) End Function   可以看到RecoveryFunction1函数中有4个参数:Object、Method、Arguments、retVal。接下来,就为大家详细介绍这4个帮助文档中并没有进行解释的参数。   ● Object发生错误时自动映射当前错误的QTP封装测试对象。   用法:Print Object.GetTOProperty("testObjName")。   结果:输出对象库中的LogicName。   TestObjName为隐藏属性。   ● Method发生错误时自动映射当前错误的QTP测试对象方法。   用法:Print Method。   结果:输出测试对象的方法。   ● Arguments发生错误时自动映射当前错误的所有参数。   用法:Print join(Arguments, ", ")。   结果:输出所有参数,以逗号分隔。   注意:由于Arguments是一个数组,因此,这里直接使用join连接数组。   ● retVal发生错误时自动映射当前错误的具体错误信息。   用法:Print DescribeResult(Result)。   结果:输出详细错误信息。   注意:retVal是Long类型,所以不可直接输出,必须要使用DescribeResult方法进行转化。   小结:   一旦场景恢复被激活之后,它就会去查找那个自定义场景恢复文件中的对应的函数,从而分别打印出当前测试对象的关键字、当前测试对象的方法、当前错误的所有参数,以及当前错误的具体错误信息。 2.7.4  总结   场景恢复这项技术对于框架中的错误定位处理非常实用,结合本书的Setting保留对象的隐藏模式里介绍的行数定位即可完美解决一系列的错误定位问题,并且本技术在终章“自动化测试框架展示”部分也使用到。   请读者自行尝试不同的场景恢复(本章节不再设置练习题),多去设计“场景恢复”,多去触发“场景恢复”,不断地增加自己的实战经验。 3.2  HTML DOM测试应用   阶段要点   ● DOM的简单介绍。   ● DOM在QTP中使用的时机。   ● DOM对象与IE对象模型的结合应用。   ● DOM在Web测试中的具体应用。   ● DOM在Web测试中的显著优势。 3.2.1  了解DOM在QTP中应用的好处 3.2.1.1  什么是DOM   DOM全称为“Document Object Model”,从字面上来看叫做“文档对象模型”。它是一款主要应用于Web HTML中的一种独立的语言。在如今的网络时代,DOM一直被广泛应用着,基本上在任何地方都能够看到它的身影,随意打开一个网页的源代码就能找到。HTML DOM主要通过定义一套标准的对象通道接口,使得我们能够轻松地访问并控制HTML对象元素,它是一种用于HTML和XML文档的编程接口。作为开发人员可以通过DOM非常方便地控制文档对象中的属性、方法和事件。DOM的表现方法是一种树形结构。所有网页中最外层的元素都是,称作DOM的顶层元素,里面还会有很多的子元素,这些元素都会把它视作为一个个节点,也就是node。通过Document对象就可以操控整个文档中的所有节点对象。下面是一段最简单的Dom应用脚本:   在脚本中可以看到顶层元素为,这个之前已经提过,接着就是头和身体两大元素,在body中可以看到一个子元素button,此元素含有一个点击事件,点击后即会触发script节点下的helloworld函数。此处函数中调用了document对象的write方法,当把以上代码保存为html文件后,点击button后即会执行helloworld方法,调用document.write,整个页面被写入test。那作为document这个可以操控整个文档对象的总入口究竟有哪些方法呢,让我们来看一下。   首先需要确保计算机里已经安装了Office。其次打开Office安装目录下的Office11\MSE7.EXE文件,此程序不管是对于DOM的查阅还是编写都是非常有帮助的。还有一个好处就是,不用另外安装其他软件,十分方便,接下来我们就来看一下它的威力。   新建一个HTML PAGE,输入上述代码,当想调用document对象的方法的时候,可以同时看到其他部分对象属性和方法,如图3-8所示。 图3-8   从图3-8所示可以看到此工具可以获取到document的部分属性和方法,以及进行代码提示,从而提高了开发人员编码的效率。由于很多工具都没有dom的提示,因此,在此处推荐这个Office附带的小工具。注意,为什么此处一直说是部分属性和方法,因为还有很多对象和方法被隐藏了起来,不过还是可以通过脚本定位跟踪法来查找其所有的方法。首先双击document对象,按下F1键后弹出document对象的帮助信息,如图3-9所示。 图3-9   如图3-9所示,选择All后就可以很方便地查看document下的所有方法属性事件了,并配有相应的语法说明和实例,非常的好用。讲了那么多DOM概念,接下来看一下DOM在QTP中应用的好处。 3.2.1.2  何时在QTP中使用DOM   在使用QTP测试Web页面时,首先需要加载Web插件,随后QTP就可以顺利地识别一些标准的控件,但有些时候网页中存在一些特殊控件或者存在大量的相同控件时,可以尝试使用DOM的方式来进行控制,因为QTP只对一些标准的控件支持比较好,而有些特殊的控件QTP无法识别,导致无法对其进行操作。DOM是一种最底层的对象操作模型,使用它来控制对象不但速度快,而且可以访问很多QTP本身无法访问的东西。接下来就来一一举例说明。   1.修改控件自身接口   此方法其实已经在第一章里详细讲解过它的应用,原理就是调用了DOM对象接口来修改控件的自身接口属性,这也是QTP本身所无法做到的。在实际测试过程也是一个非常有用的技术,关键时刻可以使问题迎刃而解。   2.DOM对象下CurrentStyle对象应用   CurrentStyle 是一个可以与HTML 对象元素的style sheets进行交互的接口,它可以获取对象元素的字体名、字体大小、颜色、是否可见等。在Web测试中真对一些特殊的界面验证点时能够发挥出很大的作用。在后续的章节中会详细对其进行分析讲解。   3.性能提升   对于性能来说,DOM的执行速度会比QTP的对象库执行速度快上好几倍,这是因为DOM相当于底层的对象接口,而QTP首先需要把对象属性进行封装,然后在脚本运行时调用对象库中的对象,最后与页面上的对象进行比对,如果属性匹配才可控制测试对象。而DOM却是直接找对象进行控制。所以,性能上相对于QTP的对象库有很大的提升,不过此优势一般只有在大量的相同对象或者一些特殊情况的时候才能有明显的区别,这个也会在后续章节详细进行讲解。   注意:使用DOM时也需要注意一点,虽然DOM有很多优势,但是也不要过分依赖DOM,对象库才是QTP的核心,过分使用DOM会导致脚本维护方面相对比较繁琐,毕竟对象库维护起来是最方便的,否则Mercury也不用花费那么大的劲来开发对象库了,而且对于不熟悉DOM的自动化测试工程师来说,会增加他们的负担,所以在使用时也请注意这些细节问题。 3.2.2  IE对象模型结合HTML DOM的Web应用   许多Web应用程序都是基于IE来开发,包括现在很多安全控件,密码控件等大多都只支持IE浏览器,以及使用QTP来对Web测试对象进行操作时也基本上使用的是IE,虽然QTP现在最新的版本开始慢慢支持其他浏览器,但被测系统大多还是基于IE来测试的。因此,熟悉IE自动化模型成了学习Web自动化测试的一项重要任务。接下来看一下具体如何操作。   1.启动IE(3种常见方法)   (1)在QTP中启动IE: '在QTP中启动IE systemutil.run "iexplore.exe"   (2)使用WSH启动IE: '使用WSH启动IE Set oShell = CreateObject("wscript.shell")   (3)使用IE COM对象: '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL   小技巧:使用此方法还可以获取窗口的句柄,并通过QTP来定位浏览器,代码如下: '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '获取窗口句柄 ieHwnd=oIE.HWND '通过句柄定位浏览器并关闭 Browser("hwnd:= " & ieHwnd).Close   2.等待页面加载   可以使用IE COM的Busy属性来确认浏览器是否处于加载状态。并为后续的操作做铺垫,若是没有此步骤,后续的一系列操作可能会无效。因为,当IE浏览器还没有加载完成时,对Web测试对象进行操作可能会出现无效的情况。因此,为了测试的顺利进行,需要在脚本中加入页面等待来保证后续的操作步骤顺利进行。 '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 While oIE.Busy: Wend   接下来对比一下页面加载和没有加载的区别。   (1)添加页面加载等待脚本,最后一步oIE.Document.f.wd.value = "zzxxbb112"先不用管,会在后面的内容中讲解: '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 While oIE.Busy: Wend '百度搜索框输入 oIE.Document.f.wd.value = "zzxxbb112"   结果:执行成功,打开IE浏览器后,成功输入zzxxbb112,如图3-10所示。 图3-10   (2)不添加页面加载等待的脚本,去掉While oIE.Busy: Wend这步: '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 'While oIE.Busy: Wend '百度搜索框输入 oIE.Document.f.wd.value = "zzxxbb112"   结果:执行出错,报出“缺少oIE.document对象”的错误信息,如图3-11所示。 图3-11   原因分析:由于IE浏览器跳转url后需要一定的加载时间,如果在还没有加载页面时,就直接使用document对象是不可以的,必须等待加载完毕,才能够对document对象进行操作,因此,程序会抛出一个缺少对象的异常。  3.遍历所有IE浏览器窗口   可以使用Windows的SHELL.Application遍历所有打开的IE浏览器窗口,并在所有打开的窗口中判断其窗口是否为IE浏览器窗口后存入字典对象集合中。在这里写一个遍历所有IE浏览器窗口并且返回一个字典对象集合的Function函数。 '遍历所有IE对象 Function EnumerateIE() On Error Resume Next '创建一个字典对象并返回所有当前打开的IE集合 Set EnumerateIE = CreateObject("Scripting.Dictionary") '获取所有Windows窗口 Set oWinShell = CreateObject("Shell.Application") Set allWindows = oWinShell.Windows '遍历每个Windows窗口 For Each oWindow In allWindows '检查如果是IE就添加字典对象中 If InStr(1, oWindow.FullName, "iexplore.exe",vbTextCompare) Then EnumerateIE.Add oWindow.hwnd, oWindow End if Next End Function   接着就可以对此函数进行应用了,例如,关闭所有IE窗口: '获取所有IE窗口对象的列表 Set allIE = EnumerateIE() '关闭所有打开的IE窗口 For Each oIE In allIE.Items oIE.quit Next   结果:通过调用刚才写的函数,成功关闭所有打开的IE窗口。同样的效果,如果在QTP中只需要一行代码就可以完成,代码如下: SystemUtil.CloseProcessByName ("iexplore.exe")   4.利用DOM操作测试对象   现在已经会使用IE COM组件来对IE浏览器进行自动化的操作,但是对于浏览器页面中的测试对象IE COM是无法对其进行操作的,这个时候就需要使用HTML DOM来对其进行操作。HTML DOM是HTML Document Object Model(文档对象模型)的缩写,它将网页中的各个元素都看作一个个对象,从而使网页中的元素也可以被计算机语言获取或者编辑。 接下来看几个简单的例子。   3种方法对比:getElementByID、getElementsByName、getElementsByTagName。   (1)通过getElementByID方法获取定位对象,并对其进行操作:@@ '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 While oIE.Busy: Wend '获取Document对象 Set oDoc = oIE.Document '使用DOM对测试对象进行操作 With oDoc '搜索框输入 .getElementByID("kw").value = "zzxxbb112" '点击百度搜索 .getElementByID("sb").Click End With Set oDoc = Nothing Set oIE = Nothing   结果:运行后首先是打开浏览器跳转百度首页,等待加载完成之后,创建DOM对象,并使用getElementByID获取百度搜索框对象,并对其进行输入“zzxxbb112”后,再次通过getElementByID方法获取百度搜索按钮并进行点击,最终成功跳转搜索结果。   (2)通过getElementsByName方法获取定位对象,并对其进行操作: '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 While oIE.Busy: Wend '获取Document对象 Set oDoc = oIE.Document '获取元素名为WD的集合 Set oEdits = oDoc.getElementsByName("wd") '遍历对象并对其进行操作 For Each oEdit In oEdits oEdit.value = "zzxxbb112" Next '点击百度搜索 oDoc.getElementByID("sb").Click Set oDoc = Nothing Set oIE = Nothing   结果:使用getElementsByName和getElementByID不同,getElementByID是返回的单个对象,而getElementsByName返回的是一个元素的集合,需要通过遍历对象才能对其进行操作。  (3)通过getElementsByTagName方法获取定位对象,并对其进行操作: '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 While oIE.Busy: Wend '获取Document对象 Set oDoc = oIE.Document '获取TAG名为INPUT的元素集合 Set oEdits = oDoc.getElementsByTagName("INPUT") '遍历对象并判断文本框对其进行操作 For Each oEdit In oEdits If oEdit.type="text" Then oEdit.value = "zzxxbb112" End If Next '点击百度搜索 oDoc.getElementByID("sb").Click Set oDoc = Nothing Set oIE = Nothing   结果:使用getElementsByTagName获取TAG名,通过得到相同类型的元素及在遍历中进行判断控件类型并进行操作。   5.利用FORM名来获取对象元素   如果使用FORM名来获取对象元素会大大简化我们的脚本。首先用IE DEV查看百度的搜索框对应的FORM名,在IE DEV中查看得到FORM名为f,如图3-12所示。 图3-12 '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 While oIE.Busy: Wend '获取Document对象 Set oDoc = oIE.Document '获取FORM名为F下名为WD的元素并输入 oDoc.f.wd.value = "zzxxbb112" '获取FORM名为F下名为SB的元素并点击 oDoc.f.sb.Click Set oDoc = Nothing Set oIE = Nothing   结果:通过以上脚本可以看到代码非常简单,同样可以达到相同的效果。   6.访问Web页面的Script脚本变量   通过DOM可以直接访问Web页面中的JavaScript或者VBScript中的变量。   首先打开百度的源文件,如图3-13所示。 图3-13   可以看到,在百度源文件中的JavaScript脚本中定义了一个变量为W,并且赋值为document.f.wd,从上面的例子可以很明显地知道,此处的W代表的就是百度搜索框这个对象,那么就可以直接操控W这个对象来对百度文本搜索框进行自动测试。 '使用IE COM启动IE Set oIE = CreateObject("InternetExplorer.Application") oIE.Visible = True '设置可见 oIE.Navigate "http://www.baidu.com" '跳转URL '等待IE页面加载完毕 While oIE.Busy: Wend '获取Document对象 Set oDoc = oIE.Document '获取百度搜索框对象 Set oEdit=oDoc.parentWindow.w '并对其进行输入 oEdit.value = "zzxxbb112" '获取FORM名为F下名为SB的元素并点击 oDoc.f.sb.Cclick Set oDoc = Nothing Set oIE = Nothing   从以上代码可以看到,只需要通过parentWindow来访问Web页面Script中的变量即可。   7.总结   这一章主要介绍了利用IE的COM以及HTML DOM来自动化IE浏览器,以及对浏览器的一些控件对象进行自动化的操作,包括启动IE、等待页面加载、遍历所有IE窗口、利用DOM操作测试对象、利用FORM名来获取对象元素、访问Web页面的Script脚本变量、Browser对象转化Window窗口对象、自定义浏览器应用程序,这些方法对于我们在自动化测试中也是起到比较重要的作用,并且能够辅助我们更好地完成Web自动化测试,当QTP不能达到我们想要达到的目的时,就可以使用这些方法来代替或者说来实现需要实现的方法,最终使Web自动化测试变得更加的轻松和容易。 3.2.3  DOM在QTP Web测试中的应用 3.2.3.1  如何在QTP中使用DOM   前面已经简单介绍了DOM在QTP中的好处,以及DOM与IE对象模型结合使用的应用,那么这一节就来看一下具体如何在QTP中使用DOM来获取并控制测试对象。   当在编写QTP脚本时,首先该做的就是将需要控制的测试对象添加到对象库,添加完毕后即可使用QTP的封装方法来控制测试对象。如果需要在QTP中访问DOM,就只需要使用Page对象,并调用Page对象的Object封装属性,QTP就能够访问到顶层DOM对象。   QTP中访问DOM对象的方法: '获取DOM对象 Set sDom = Browser(" ").Page(" ").Object   此处的Object属性目前只支持IE,而对于其他浏览器目前还没有加入支持,通过DOM,可以在QTP中修改HTML元素对象的属性,调用其方法,当在使用Web测试对象的Object属性时,事实上就已经自动获取到DOM对象的一个引用,这就意味着可以调用测试对象的DOM引用下的所有的属性、方法。作者一直认为QTP的这个功能做的非常不错。毕竟有了DOM可以解决很多QTP本身无法解决的问题。在了解了怎样在QTP中访问Document对象之后,就来看一下QTP在Web测试中具体是如何应用,如何来操控各类HTML元素的。 3.2.3.2  如何在QTP中使用DOM操控各类HTML元素   1.WebEdit对象   HTML源代码样例:   QTP中代码样例。   操控方式1: ‘获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object ‘使用GetElementByID获取对象元素,并更改文本框WebEdit的value属性值 oDocument.getElementById("ID_001").value = "zzxxbb112"   操控方式2: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '使用GetElementsByName获取对象元素,并更改文本框WebEdit的value属性值 oDocument.getElementsByName("NAME_001")(0).value = "zzxxbb112"   操控方式3: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '如果ID和name都是空的,可以使用getElementsByTagName Set allElements = oDocument.getElementsByTagName("INPUT") For each element in allElements If element.value="QuickTest" Then element.value="zzxxbb112" End If Next   2.WebButton / Link对象   HTML源代码样例:   QTP中代码样例。   操控方式1: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '使用GetElementByID获取按钮对象并触发onclick事件 oDocument.getElementById("ID_001").click  操控方式2: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '使用GetElementsByName获取按钮对象并触发onclick事件 oDocument.getElementsByName("NAME_001")(0).click   操控方式3: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '使用All方法获取按钮对象并触发onclick事件 oDocument.all("ID_001").click   3.WebCheckBox 对象   HTML源代码样例:   QTP中代码样例。   操控方式: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '使用GetElementByID方法获取按钮对象,选取checkbox oDocument.getElementById("ID_001").checked = true   4.WebList / WebComboBox 对象   HTML源代码样例:   QTP中代码样例。   操控方式: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '使用GetElementByName方法获取List对象, set ListObjElements = oDocument.getElementsByName("NAME_001").item(0) '遍历List列表,根据关键字选取List项 For i=0 to ListObjElements.Options.length-1 If ListObjElements.Options(i).value = "child_002" Then ListObjElements.Options(i).selected = true End If Next   5.WebRadioGroup 对象   HTML源代码样例: 女   QTP中代码样例。   操控方式: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '通过GetElementsByName获取对象元素后选取Radio单选按钮 oDocument.getElementsByName("sex").item(1).checked=true   6.WebTable对象   HTML源代码样例:
A1 B1
A2 B2
  QTP中代码样例。   操控方式: '获取DOM对象 set oDocument = Browser("Browser").Page("Page").Object '获取WebTable对象元素 set objTable = oDocument.getElementById("ID_001") '打印WebTable的行数 msgbox objTable.rows.length '打印WebTable的单元格数量 msgbox objTable.cells.length '打印WebTable第二行第一个单元格中的内容 msgbox objTable.rows(1).cells(0).outertext   注意:此处表格对象含有两个对象集合:Rows (所有行)和Cells(所有单元格)。 3.2.3.3  利用DOM完成QTP无法完成的任务   使用CurrentStyle验证对象   HTML源代码样例:
        

DHTML using DISPLAY

  QTP中代码样例: '获取DOM对象 set oElementDocument = Browser("Browser").Page("Page")._ WebElement("html id:=id_001").Object isVisible = oElementDocument.currentstyle.visibility If isVisible="hidden" Then msgbox "object is hidden" else msgbox "object is visible" End If   注意:   此处如果使用QTP的exist方法,结果永远是返回True。因为此对象的确是存在于网页中,但是它被设置为了不可见,而exist方法只是验证对象是否存在,却不能验证是否隐藏。这样就无法达成我们需要验证的目的,而document对象下的currentstyle可以直接访问style sheets,而且QTP从10.0版本开始已经支持currentstyle下的所有属性,提高了编码的效率,如图3-14所示。 图3-14   除了验证对象是否为可见或者隐藏外,currentStyle还可以验证许多QTP本身无法验证的属性,如字体名、字体大小、字体颜色等。在实际项目测试工作中,此对象也是经常被使用到,也请读者能够牢记,更多验证属性可参见msdn.microsoft.com中的currentStyle对象。 3.2.3.4  利用DOM提升性能   当对象数量较多时,使用DOM较为占优势,数量越多就越明显,这里简单做个对比假设一个页面一共有100个文本框,我们的需求是对这100个文本框分别输入helloworld这个字符串。为了能更好地让读者体现DOM在性能方面的优势,此处就自己构建一个含有100个文本框的HTML页面,每个文本框的name属性都是由text_开头,之后由1到100递增,脚本如下所示:                                                                            
           把以上脚本保存为HTML文件后,打开此文件,并打开QTP对象库,把PAGE对象加入到对象库中,为了能够更直观地看到最终的对比结果,再次特别加入了QTP中保留对象Services的Transaction属性,此方法主要用于计算事务时间的。来看两段不同的脚本。   QTP描述性编程脚本: Services.StartTransaction "inputBefore" For i=1 to 100           Browser("Browser").Page("Page").WebEdit("name:=text_"+cstr(i)).Set "zzxxbb112" Next Services.EndTransaction "inputBefore"   DOM操作脚本: Services.StartTransaction "inputBefore" For i=1 to 100           Browser("Browser").Page("Page").Object._           getElementsByName("text_" + cstr(i))(0).value =  " zzxxbb" Next Services.EndTransaction "inputBefore"   第一个脚本是使用了QTP的描述性编程,并对text后的动态数字进行了参数化,这样QTP就能顺利地对每个文本对象进行set方法的操作。第二个脚本使用了page对象的object属性,也就是获得了DOM对象的引用,接着使用GetElementsByName方法,动态循环获取文本框并进行输入。其实相信读者在运行以上两段代码时就应该深有体会,为了让读者更清晰地看到两种操作对象方法的性能差距,这里特地使用了事务,并在结果报告中进行了时间输出。   QTP描述性编程的结果,如图3-15所示。 图3-15   DOM操作脚本的结果,如图3-16所示。 图3-16   分析:   由如上两张图可以看出,第一张图使用QTP描述性编程测试结果总消耗时间为20.4秒,而第二张图使用DOM操作脚本的结果图总消耗时间为5.7秒,经过对比分析后,可以得到的结论是,当需要输入100个文本框时,DOM对象的性能要比QTP描述性编程方式快2~4倍,这个差距其实是非常惊人的,此处读者也可以想象一下,现在是100个文本框,若是有500个或者1000个,它们之间的差距又会相差多少。如果有兴趣的读者可以尝试一下后两种情况甚至更多,最终的性能提升差距可能会更大。 3.2.4  总结   本章节主要让读者了解了DOM在自动化测试过程中的优势,以及如何在QTP中使用、何时使用等。在实际项目应用过程中,只要是Web应用基本上都会有它的身影,而且它与测试对象的关系非常的紧密,可以脱离QTP操控Web对象元素的自身接口属性或者方法。因此,DOM也算是必会的一个知识点。很多时候它也能够解决QTP无法解决的问题。本章所提到的几个优势只是实际应用中的一部分,还有许多优势只有自己在接触真实项目之后才可将其挖掘出来。例如,当遇到日期控件,文本框是disable状态的,类似这样的情况可以使用DOM接口访问控件的value属性直接注入需要的值。只要把DOM的作用充分发挥出来并与QTP完美结合,相信大多数测试对象问题一定都能迎刃而解。   知识点巩固和举一反三练习   一、利用DOM使支付宝的密码框变为只读。   步骤:   1.打开www.alipay.com,找到密码框。   2.利用DOM技术使其密码框状态变为Disable。 3.3 数据库操作(ADO)   阶段要点   ● 何时使用ADO数据验证。   ● 构建数据库连接字符串的两种方式。   ● 利用ADO进行数据库操作。   ● 建立自定义动态数据库验证函数。 3.3.1  何时使用ADO数据验证   数据库验证可能对于有些自动化测试工程师来说基本不太会使用到,因为对于自动化测试来说,主要验证的是前台的数据和功能,也就是最贴切用户的角度来测试。因此,对于后台的数据验证来说,自动化测试在一般情况下可能不需要那么严格来做这方面的验证。但是如果是在金融行业可能会深有体会,因为所有的测试用例只要和金额有关,都需要对其数据库进行验证,原因大家一定也都明白,对于保证金额正确性是公司最为重视的,若是一不小心就可能造成一定的资损,在这方面的测试都特别的严格。正由于其本身的这个特殊性,数据验证便成了自动化测试中的关键所在,在验证前台数据的同时还必须对后台的数据库进行验证才能算是一个完整严格的自动化测试用例。   大家都知道QTP工具本身提供了数据查询和验证功能,但是使用过的读者一定会感觉到,其功能使用起来比较麻烦,灵活性和复用性较低,那么是否有一种方法可以即灵活又快速地进行数据操作和验证呢?这就是ADO数据库操作对象组件,由于QTP基础平台脚本语言是VBScript,因此,可以利用VBScript脚本语言调用ADO组件来进行数据操作,由于是脚本的形式来进行操作,优势显而易见。首先脚本的灵活性较高,可实现的功能也较多,可以随意地把需要实现的数据操作封装为函数,以便下次使用时可以直接调用此函数进行复用,提高测试的效率。介绍了那么多,接下来就看一下利用ADO操作数据库的3大主管对象。   ADOMicrosoft's?ActiveX Data Objects?   简单地说ADO就是一个可以访问并操作数据源的一个COM组件。开发人员可以通过此组件并通过脚本的形式对数据库进行简单的操作。   Connection对象。   此对象为ADO的第一大重要对象,它主要是掌管数据库的连接和关闭功能,有了它就可以通过对应的连接字符串来连接数据库,完成ADO操作的第一步工作。   Command对象。   此对象的作用主要是完成SQL语句的执行,包括查询语句、更新语句、创建语句、删除语句以及存储过程。   RecordSet对象。   此对象主要作用是存放执行后的数据结果集。当一个执行语句被执行后并返回给RecordSet对象之后,此时RecordSet对象就包括了当前所有执行结果集,并且可以通过Eof方法循环进行输出,此方法会在后续的内容中详细进行讲解。 3.3.2  构建数据库连接字符串的两种方式   在上一章中已经介绍了ADO操作数据库的3大主管对象,接下来的工作就是要详细解释这3大对象的用法。在讲这之前,先来看一下这个简单的连接数据库的例子: Dim adoConn '定义ADO连接对象 Dim ConnectionStr '定义数据库连接字符串 '获取数据库连接字符串 ConnectionStr = "Driver=MySQL ODBC 5.1 Driver;"+ _ +"SERVER=localhost;UID=root;PWD=root;DATABASE=test;PORT=3306" '创建数据库连接对象 Set adoConn = CreateObject("adodb.Connection") '利用数据库连接字符串打开数据库 adoConn.Open ConnectionStr '****************** '此处进行数据库操作 '****************** '关闭数据库 adoConn.Close '释放数据库对象 Set adoConn = Nothing   分析:   从以上脚本可以看到,首先定义了一些ADO所需要的变量,一个是ADO对象,另一个是数据库连接字符串。其次创建了数据库连接对象,并打开数据库,对其进行操作后,关闭数据库并释放ADO对象。就在这样一个简单的脚本中,大家有没有注意到一个细节,关于ConnectionStr这个变量的值,也就是数据库连接字符串。可以看到这个字符串非常长,非常难记住,而且不同数据库的连接字符串也都完全不相同,若是每次都手工写的话不但容易出错,而且很有可能会有遗漏,况且此连接字符串可是连接数据库的关键密钥,若是出了问题,整个数据库的连接就会出错。因此,问题就转向了本小节的标题,也就是如何来获取ADO数据库连接的字符串。此处提供两种比较便捷的方法供读者选择。  方法一:Data Link获取。   Data Link方式是基于Microsoft Access Service的数据源配置文件。首先就来创建一个udl格式的文件,此处命名为“adoCon.udl”,接着双击此文件,如图3-17所示。 图3-17   如图3-17中,可以发现当双击了adoCon.udl后,程序自动弹出了数据链接属性窗口,可以看到窗口选择对应的连接方式,这里的目的是获取连接字符串。接着就需要选择图3-17中标注的“使用连接字符串”单选框,点击“编译”按钮,同样见图3-17的方框标注位置。   如图3-18所示,弹出选择数据源窗口,需要选择连接数据库的文件数据源,如果没有建立过任何数据源,可以点击新建按钮,如图3-19所示。 图3-18 图3-19   在图3-19上,选择相应的数据源后,点击下一步,填写数据源名称后就可以成功创建数据源文件,如图3-20所示。 图3-20   在弹出选择数据源窗口中,选择需要连接数据库的文件数据源,此处使用的是mysql的数据源,点击确定后,并通过连接测试,如图3-21所示。 图3-21   如图3-21所示,在连接字符串处标注的文本框中可以看到所需要的字符串已经被自动填入。剩下工作就非常简单了,只需要对其内容使用复制、粘帖来完成ADO的数据库连接字符串。此时如果对此文件使用记事本打开后也可以看到更多的细节,如图3-22所示。 图3-22   方法二:巧用QTP获取。   使用QTP获取的方法来获取数据源的连接字符串更加便捷,只需要通过dataTable这个中间介质作为入口,即可轻松获取到想要的结果。首先启动QTP,并打开DATATABLE视图,在DataTable中,任意在一个单元格中点击右键逐层进入到From DataBase,如图3-23所示。 图3-23   在窗口DataBase Query Wizard中选择Specify SQL statementmamal后,直接点击下一步,在弹出的对话框找到create按钮,并进行点击,还是一样选择对应的数据源,若没有数据源就自行先创建一个,创建完毕并“选择数据源”后,进行数据库连接测试,测试通过点击确定。这样,QTP的DataBase Query Wizard就会自动获取到相应的数据库连接字符串,如图3-24所示。 图3-24   图3-24中红色标记的地方就是最终所需要的语句,此处主要是利用QTP的Database Query Wizard作为一个获取数据库字符串的工具。两种方法各有千秋,读者可自行选择使用任一种方法。 3.3.3  数据库查询   在前面两个小节中已经简单介绍了ADO对象,以及3大主管对象(Connection、Command、RecordSet),并且也已经把如何快速并方便地获取到数据库字符串的方法提供给了读者。从这一节开始就要开始进入ADO这一章的重头戏,也就是数据库的自动化操作,这里需要把前面所讲过的知识都串起来,并通过一个最简单的数据库查询的例子来让大家掌握,如何正确地使用ADO来对数据库进行操作。   实例1:使用where语句查询某数据表的某个字段值   (1)需要有一个数据库来支持我们的脚本。这里直接使用mysql数据库。   (2)首先在数据库里新建一个数据表,如图3-25所示。 图3-25   (3)创建ADO组件来查询数据表中name为zzxxbb112的age是多少: Dim adoConn '定义ADO连接对象 Dim ConnectionStr '定义数据库连接字符串 '获取数据库连接字符串 ConnectionStr = "Driver=MySQL ODBC 5.1 Driver;"+ _ +"SERVER=localhost;UID=root;PWD=root;DATABASE=test;PORT=3306" '获取数据库查询语句 sqlStr = "select * from user where name = 'zzxxbb112' " '创建数据库连接对象 Set adoConn = CreateObject("adodb.Connection") '利用数据库连接字符串打开数据库 adoConn.Open ConnectionStr '执行sql语句并返回对应的结果集 Set adoRst = adoConn.Execute(sqlStr) '获得结果集中年龄字段的值 MsgBox adoRst.Fields.Item("age").Value '关闭数据库 adoConn.Close '释放数据库对象 Set adoConn = Nothing   分析:   以上脚本首先是定义了连接字符串和SQL语句字符串这两个变量,接着就是创建ADO.Connection对象,通过数据库连接字符串打开数据库连接,这些都和上一小节中的例子一样,不过接下来这句Execute就是最关键的地方了,在ADO的3大主管之一的connection对象中存在一个Execute方法,方法参数为SQL语句字符串,此方法可以对数据库直接进行SQL语句执行操作,并且能够对其执行结果进行返回,返回的对象为3大主管对象的另一个对象RecordSet。最后通过RecordSet对象的Field属性获取到age字段的值。最后再关闭数据库,释放数据库对象,这样一个最简单的数据库查询的例子就完成了。   在上面的例子中,如果遇到一些比较特殊的情况,可能脚本就会失效了,举个最简单的例子,之前那个实例是“使用where语句查询name为zzxxbb112这条记录中的age字段值”,那么现在重新更改一下要求,内容为“使用where语句查询age为50的name值”,细心的读者一定会发现,目前数据表有两条age为50的,通过select语句也可以查到:“select * from user where age=50;”,如图3-26所地。 图3-26  从图3-26中可以看到数据库中一共有两条age为50的记录,那么在脚本中只有一个MSGBOX来打印出结果。读者会说那就再重复打印一次field语句,那么就在原来那行MsgBox adoRst.Fields.Item("age").Value脚本下再复制一行: '获得结果集中年龄字段的值 MsgBox adoRst.Fields.Item("name").Value MsgBox adoRst.Fields.Item("name").Value   但是非常遗憾的是,在执行完以上脚本后,执行结果是两个值都是zzxxbb112,而不是zzxxbb112和51testing。为什么不会打印第二个?难道就没办法了吗。其实解决办法很简单,不过在解决这个问题之前,需要给读者介绍一个名词,它叫“游标”,我们都知道在测量中有游标卡尺,而游标在游标卡尺中的作用是可以移动到任何地方并测量出对应的结果数据。当然在数据库中也有这样一个概念,游标提供了一种对从表中检索出的数据进行操作的灵活手段,就本质而言,游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。当SQL语句执行完毕并获得多条结果集时,游标在初始状态下是指向第一条记录的,此处可以利用数据库游标来随意移动获取到需要的结果记录。   MoveNext方法:用于移动游标到下一条记录   那么原来的脚本只需要改成: '打印结果集的第一条记录 MsgBox adoRst.Fields.Item("name").Value ‘将游标移动到下一条记录 adoRst.MoveNext ‘打印结果集中的第二条记录 MsgBox adoRst.Fields.Item("name").Value   分析:   以上脚本其实已经完成了之前的要求,但还不是一个完整又灵活的脚本,因为现在我们是知道数据库查询结果集的个数,一旦数据集非常多的情况,又无法知道需要移动多少次游标时,这样的脚本就完不成任务了。那么应该如何去更改我们的脚本呢?在这里提供两种方法供读者参考。   方法1:FOR循环法。 ‘获取结果集的个数,并进行遍历 For i=1 To adoRst.Fields.Count ‘打印结果集中name的值 MsgBox adoRst.Fields.Item("name").Value ‘移动游标到下一条记录 adoRst.MoveNext Next   分析:   执行脚本后能够正确获取到对应的两条记录结果,此处的adoRst.Fields.Count是获取记录集的个数,通过for循环进行遍历,并在每次循环next之前把游标移动到下一条记录上。从而获取到所有对应的记录值。   方法2:EOF循环法。 ‘如果游标不为最后一条记录就一直循环 While Not adoRst.EOF ‘打印结果集中name的值 MsgBox adoRst.Fields.Item("name").Value ‘移动游标到下一条记录 adoRst.MoveNext Wend   分析:   注意,这里脚本中的EOF代表最后一条记录的意思,缩写为End Of File,翻译过来就是文件结束。那么在脚本里是作为一个文件结束的标志,也就是说,如果游标定位没有移动到末尾处,也就是最后一行就继续循环,直到游标移动到最后一条记录跳出循环。执行以上脚本结果和上面的方法1完全一样,同样也是把所有记录都一一打印出来。   这里两种方式都是可以的,就看读者比较偏向哪一种了,本人比较偏向第二种,因为第二种方法可以省去一个循环变量i,而且EOF方法是ADO内置的一种专门针对多条数据记录准备的,因此,个人认为,在性能或者是稳定性上应该比方法1好一些。 3.3.4  数据库修改   在自动化测试过程中,数据库的修改操作虽然没有查询用的那么普遍,但是在某些特定的情况下,能够使用脚本对数据库进行修改对于自动化测试来说还是非常有帮助的。举个最简单的例子:自动化测试数据初始化准备,这是重要的,因为当运行完用例脚本之后,数据库的一些数据和状态无法满足下一次脚本的运行。这样可能导致下一次再运行同一个自动化测试脚本时会出现脚本执行错误。如果要解决这样的问题,一种情况就是使用自动化脚本在用例里就直接回归原点(自动化测试用例设计那个章节中详细介绍过),例如,新建一条数据之后,如果有删除业务,就可以把删除和新增这两条用例合并作为一条自动化用例。这样就达到了回归原点的目的,在下一次执行脚本的时候就能够很好地避免脚本错误发生。如果是第一种情况也无法解决的情况下,那么就可以考虑使用ADO对象来对数据库做初始化回归操作。虽然很多情况下这类工作是由开发人员或者开发测试人员来完成的,但是作为一个合格的自动化测试工程师来说,应该也需要能够胜任这样的任务。接下来就简单介绍一下如何使用ADO对象来进行数据库的修改。   举例,修改name为yujie的那条记录的年龄age为100: Dim adoConn '定义ADO连接对象 Dim ConnectionStr '定义数据库连接字符串 '获取数据库连接字符串 ConnectionStr = "Driver=MySQL ODBC 5.1 Driver;"+ _ +"SERVER=localhost;UID=root;PWD=root;DATABASE=test;PORT=3306" '获取数据库查询语句 sqlStr = "update user set age = 100 where name='yujie' " '创建数据库连接对象 Set adoConn = CreateObject("adodb.Connection") '利用数据库连接字符串打开数据库 adoConn.Open ConnectionStr '执行sql语句并返回对应的结果集 adoConn.Execute sqlStr '释放数据库对象 Set adoConn = Nothing   分析:   读者一定已经发现了,脚本其实没有多大的变化,这里只是更改了SQL语句,从原来的“select * from user where name = 'zzxxbb112' ”变为现在的“update user set age = 100 where name='yujie' ”。值得注意的是,这里由于是纯执行修改语句,而不是查询语句,因此不用返回任何结果集,也不用关闭结果集,因为结果集对象根本就没有打开。   当然以上脚本只是最简单的修改语句,如果需要进行数据初始化准备,肯定不会只是一条SQL语句就可以搞定的,一般可能都会把所有的SQL语句全部存放在一个文件里,那么接下来看一下如何实现多行语句执行。首先当我们需要实现多行语句执行时,我相信肯定有很多读者会想,多行不是一样,直接使用分号进行隔开,再赋值给SQL语句字符串,并交给execute方法执行下就可以了,就类似于sqlStr = "update user set age = 88 where name='zzxxbb112' ; update user set age = 888 where name='yujie'" 这样,但其实运行此脚本的时候,你会发现,运行环境直接抛出一个错误,如图3-27所示。 图3-27   如图3-27所示,错误信息中提示说,脚本中存在一个错误,SQL的执行语句存在语法错误。ADO方式在mysql数据章中使用这种方式进行多行SQL语句执行是行不通的。既然这样的方式行不通,可以用另一种方式来解决。这就是本章讲的重点“循环执行法”。  循环执行法—把所有的SQL语句分割成数组,并把数组全部取出进行循环执行SQL: Dim adoConn '定义ADO连接对象 Dim ConnectionStr '定义数据库连接字符串 '获取数据库连接字符串 ConnectionStr = "Driver=MySQL ODBC 5.1 Driver;"+ _ +"SERVER=localhost;UID=root;PWD=root;DATABASE=test;PORT=3306" ‘获取sql语句数组集合 SQLCommands = GetSQLCommands("d:\sql.txt") '创建数据库连接对象 Set adoConn = CreateObject("adodb.Connection") '利用数据库连接字符串打开数据库 adoConn.Open ConnectionStr ‘循环执行sql语句 For i=0 To UBound(SQLCommands) adoConn.Execute SQLCommands(i) Next '关闭数据库 adoConn.Close '释放数据库对象 Set adoConn = Nothing '******获取文本文件中的数据库查询语句集********* Function GetSQLCommands(sqlPath) '创建fso模型对象 Set fso=CreateObject("Scripting.FileSystemObject") '获取txt文本对象 Set txtfile = fso.OpenTextFile(sqlPath) '获取txt中的所有文本 allSQLCommand = txtfile.ReadAll '对所有文本通过回车进行分割,把sql语句分割成数组 sqlcommands = Split(allSQLCommand ,Chr(10)) '返回所有sql语句数组 GetSQLCommands = sqlcommands '释放txtfile Set txtfile = Nothing '释放fso Set fso = nothing End Function '******************** END *********************   分析:   执行以上脚本,只需要另外再准备一个SQL语句的文本文件,会发现这样的脚本是把数据和脚本做了完全的分离,就相当于动态地取SQL语句。就是数据库初始化准备的SQL脚本发生了变化,也不会对脚本产生任何影响。因为所有脚本都是动态读取文本文件的,而一旦数据库准备的SQL语句发生变化,只需要直接更改SQL文本文件即可。   注意,此处使用换行符chr(10)来进行分割是因为在这里读取的SQL文件的语句为换行分割的,如果是分号的,就用“;”来分割,这个看个人习惯了,一般使用回车符来分割语句比较常见。 3.3.5  自定义动态数据库验证函数   在本章的前几个小节已经把ADO的一些比较常用和基本的用法都介绍了,那么有了技能就需要开始进一步的实践了,一切都是为了最终的项目实际应用。那么接下来看一下实际的自动化测试过程中是如何来规范数据库验证的。   首先,要做数据库验证,先要选取对应的方法,数据库验证有两种,一种是QTP自带的数据库查询来验证,另一种就是整个章节一直在讲解的ADO组件。这里就选择ADO组件来作为验证方法,具体原因已经在第一小节中讲解过。   其次,还需要注意数据库是各种各样的,每个数据库连接字符串会有所区别,需要另外定制,这在第2小节的构建数据库连接字符串里已经详细讲解过,面对这样的情况就必须把这些数据作为变量分离出来,以便随时可以进行方便的配置。而这里常常会使用环境变量或者自定义外部XML文件作为一个数据配置文件来配合自动化数据验证。   最后,还需要考虑的是,数据库验证是属于测试流程中的一个重要的步骤,使用的频率也非常高,数据库验证的这个过程如果可以复用,那么就可以省掉很多多余的工作,提高了数据库验证的工作效率。   实例:使用QTP来验证数据库user表中name为51testing的age   预期:age = 50。   在这里直接选取之前第3小节中的实例,并在此基础上进行数据抽离和封装,脚本如下所示: '************************************************************** 'Description: 数据库验证函数 'Argument: '[Constr] 数据库连接字符串 '[sqlStr] sql语句 '[verifyProperty] 需要验证的字段名 '[expectValue] 验证预期值 '************************************************************** Function VerifySQLData(conStr,sqlStr,verifyProperty,expectValue) '创建数据库连接对象 Set adoConn = CreateObject("adodb.Connection") '利用数据库连接字符串打开数据库 adoConn.Open conStr '执行sql语句并返回对应的结果集 Set adoRst = adoConn.Execute(sqlStr) '获得实际值 actualValue = adoRst.Fields.Item(verifyProperty).Value '对比预期值与实际值 If cstr(expectValue) = cstr(actualValue) Then Reporter.ReportEvent micPass,"SQL_DATA_CHECK", _ "[ expectValue = actualValue = " + cstr(actualValue)+" ]" Else Reporter.ReportEvent micFail,"SQL_DATA_CHECK", _ "[ expectValue = " + cstr(expectValue) + _ " ;actualValue = " + cstr(actualValue)+" ]" End If '关闭数据库 adoConn.Close '释放数据库对象 Set adoConn = Nothing End Function   分析:   从以上脚本中可以看出,之前的脚本已经被封装成了function函数,并且一共抽离出了4大参数:conStr、 sqlStr、verifyProperty、expectValue 。constr为数据库连接字符串,由于数据库可能会变化,因此作为参数处理,sqlstr为SQL语句,这个肯定需要分离,这样可以随意定制需要的查询语句,verifyProperty以及expectValue为需要验证的数据结果集的字段名和它的预期值,这两个参数也是必不可少的。最后在脚本的中间加上了判断,并利用QTP的保留对象Reporter写入执行结果中。这样,一个数据库验证函数就全部封装完毕了。  函数封装完毕之后,在调用函数之前,必须要把一些关键参数进行环境变量的配置,以便以后有任何变更可直接在配置文件里进行修改,而不是去更改脚本中的数值。假设现在构建一个路径,并建立一个XML文件,格式支持环境变量形式:"D:\QTP Config\SQLConfiguration.xml",文件内容如下: ConnStr Driver=…;SERVER=…;UID=..;PWD=…;DATABASE=…;PORT=… sqlStr select * from user where name = '51testing'   在完成了环境变量配置文件之后,此处直接使用动态调用的方法来调用环境变量的XML文件: ‘动态载入环境变量 Environment.LoadFromFile "D:\QTP Config\SQLConfiguration.xml"   此方法的具体解释可见本书的环境变量那章。那么既然载入了环境变量后,就可以顺利调用数据库验证函数了。 ‘***********调用验证数据库函数********** ‘验证字段:Age ‘验证预期:50 ‘*************************************** VerifySQLData Environment.Value("ConnStr"), _ Environment.Value("sqlStr"), "age" , 50   执行结果如图3-28所示。 图3-28   如图3-28所示,执行完脚本后,结果报告显示Passed通过,并显示预期值等于实际值为50。最后再来执行下select语句,查询并验证一下最后数据表中的数值是否为50,如图3-29所示。 图3-29   从图3-29中可以明显看到值为50,因此,可以确定验证的函数没有问题。简单总结一下,这一节主要讲解了一个实际项目中,如何使用数据库动态验证的例子,如何封装函数,如何构建外部配置环境变量,如何进行动态调用环境变量等,这些都是在数据库验证中必不可少的方法,也请读者能够深刻理解并掌握这一节的内容。 3.3.6  总结   数据的验证与校验对于自动化测试过程来说是一个非常重要的阶段,因为一个严谨的测试用例,数据验证是自动化测试的核心,如果没了数据验证自动化测试还会有任何意义吗。所以,对于做好数据验证是学习自动化测试的重中之重。QTP对数据库开发支持较少,但是可以利用它强大的后台语言VBS来支持开发动态数据库验证函数,并结合环境变量进行外部配置。希望读者能够熟练地运用好此验证模式。   知识点巩固和举一反三练习   一、请尝试使用3.3.2小节中的知识点构建数据源为QT_Flight32的数据库连接字符串   提示:用户名:admin密码:mercury。   二、请尝试使用本文中的自定义数据库验证函数模式应用到自己的项目中   注:本题无答案。 3.4  Excel—数据驱动必备利器   阶段要点   ● EOM模型简介。   ● 熟练掌握EOM对Excel的自动化操作。   ● 详细分解如何动态加载宏代码并应用于自动化过程中。   ● 实现更多高级的Excel与QTP的联动技巧。 3.4.1  利用EOM自动化EXCEL 3.4.1.1  EOM模型简介   EOM:全称为Excel Automation Object Model 即为Excel的自动化模型对象   在自动化测试过程中,经常会把数据和脚本进行分离,也就是平时所说的数据驱动模型,而在QTP中经常会使用参数化的方式,并结合QTP的DataTable对象来对数据表进行操作和储存数据。那读者一定会问,既然QTP已经存在了对数据表的操作,并且已经可以非常容易地使用了,为什么这里还要介绍EOM呢?其实本人也和大家想的一样,对于QTP的DataTable已经可以非常出色地满足基本的需求了,并且mercury当初对这块东西也封装的不错,使用过的朋友觉得很好用。但是作为一个自动化测试工程是来说,是否想过一个问题:在大多数情况下数据准备、环境准备都是由自动化测试人员来完成的,当我们手里有一份数据量较大的数据表,并且此数据表的格式并不是符合QTP读取DataTable的规范格式,那下一步该怎么办呢?   这里提供两种方式供大家选择。   ● 通过编写Excel Macros把数据表的格式转化为QTP中DataTable的格式。   ● 直接抛弃DataTable功能,使用EOM来对数据表进行操控。   两种方式都是不错的解决方案,但是个人比较喜爱第二种。因为第一种方式中间多了一个转化Excel格式的步骤,第二种方式直接根据数据表格式进行数据读取。读者可以根据自己的实际情况进行选择使用。接下来就看一下EOM到底有哪些“绝活”可以把Excel“玩于鼓掌之中”。 3.4.1.2  Excel Automation   前面已经讲到了EOM是一个可以自动化Excel的自动化模型对象,既然需要使用EOM就必须先要对其进行引用,引用完毕之后才可以对其封装的所有方法进行调用,并对Excel进行自动化。当然引用一个COM组件对象时,必须要确定此组件对象的组件名和类名,Excel的组件名和类名分别为Excel和Application。确定好这些之后,就可以创建Excel的自动化模型了,脚本如下所示: ‘创建Excel自动化模型对象 Set excelApp = CreateObject("excel.application")   执行结果:   当在VBSedit的编辑区域里输入以上脚本后,可以看到在VBSedit的Object Browser视图中自动添加了一本书的图标,如图3-30所示。 图3-30   从图3-30中可以看出,Excel自动化模型已经被成功引用加载到VBSedit中,也就是说EOM下的所有封装方法和对象都已经可以调用。为了能够顺利地讲解后续内容,此处首先要让读者明白两个概念,这两个概念就是Book和Sheet。虽然这是一个比较基础的概念,但经常发现许多同行会混淆这个概念。   Book与Sheet的区别。   Book是Excel的一个最大的模块单元,具体点就是一个Excel的文件,也就是一个Xls文件。这里需要注意的是,Excel Book和Word Book两者之间有着一个非常大的区别。细心的读者应该就会在平时的操作中发现,在打开多个Excel Book时(也就是打开多个xls文件),Excel会自动把当前所有打开的Excel Book都归类在一个主窗口中,这个主窗口同时也已经绑定了所有的子Book窗口,要创建Excel Book就必须要有主窗口,要关闭主窗口就必须关闭所有子窗口。也正是因为这个原因,很多时候才会遇到,当关闭一个主窗口时,会提示保存所有的Excel Book。Sheet是Book下的一个分类子模块,在每个Book下默认都会有3个Sheet,用户可以自动进行添加与删除。   在搞清楚Book与Sheet之间的区别之后,就可以进入到下一个环节,也就是如何通过EOM创建Excel工作表了。在EOM中有一个对象为WorkBooks对象,那么Book对象前面也已经进行了详细的介绍,这里的WorkBooks对象主要是用来管理Excel的Book对象,包括新增、打开。来看一下脚本是如何实现的。   1.WorkBook操作   前面已经对WorkBook和Sheet的区别作了一个简单的介绍,接下来就来看一下WorkBook对象有哪些作用和方法。   (1)新增WorkBook: '创建Excel自动化模型对象 Set excelApp = CreateObject("excel.application") '设置为可见模式 excelApp.Visible = true '新增WorkBook excelapp.Workbooks.Add '保存 excelApp.Save "d:\1.xls" '关闭Excel excelApp.Quit '释放Excel对象 Set excelApp = nothing   执行结果:   以上脚本的执行结果很简单,就是新建一个WorkBook后,保存并关闭。此处需要注意的是可见模式的设置,如果遗漏Visible属性设置,执行过程会在后台进行,前台不会进行显示。如果在代码的最后没有关闭Excel,在第二次创建EOM时,后台就会多出一个Excel的进程,如果继续创建EOM对象,后台的Excel进程就会越来越多,而我们却无法对其进行操控。此时能做的只有关闭这些进程。方法很简单,直接通过任务管理器结束相应的Excel进程,如图3-31所示。 图3-31   如图3-31所示,找到对应的Excel.exe进程名并选中,点击结束进程即可。   (2)打开WorkBook: '创建Excel自动化模型对象 Set excelApp = CreateObject("excel.application") '设置为可见模式 excelApp.Visible = true '打开WorkBook excelapp.Workbooks.Open "d:\1.xls" '释放Excel对象 Set excelApp = nothing   执行结果:   此脚本很简单,执行结果就是直接打开了D盘中的1.xls文件,前提是文件必须存在,如果文件不存在,则会出错。因此,可以在代码中利用fso进行事先的文件是否存在的判断。  (3)获取WorkBook的数量: '创建Excel自动化模型对象 Set excelApp = CreateObject("excel.application") '设置为可见模式 excelApp.Visible = true '打开WorkBook excelapp.Workbooks.Open "d:\1.xls" '获取WorkBook的数量 MsgBox excelapp.Workbooks.Count '释放Excel对象 Set excelApp = nothing   执行结果:   此脚本中的Count方法主要是获取WorkBook的数量,脚本执行过程;先打开对应路径的Excel后,获取对应的工作簿数量。这里需要注意的是,工作簿数量其实就是当前打开的Excel数量,相当于进程中的xls数量,也请读者不要搞混了。   2.WorkSheet操作。   (1)新增Sheet: '创建Excel自动化模型对象 Set excelApp = CreateObject("excel.application") '设置为可见模式 excelApp.Visible = true '打开WorkBook excelapp.Workbooks.Open "d:\1.xls" '为1.xls新增一个Sheet Set oSheet = excelapp.Sheets.Add '重命名Sheet为"helloworld" oSheet.name = "helloworld" '释放Excel对象 Set excelApp = nothing   执行结果:   新增了一个名为“helloworld”的Sheet,运行完毕之后可以看到,当前的WorkBook一共有4个Sheet,其中一个就是刚才新建的名为“helloworld”的Sheet,其余3个Sheet是Excel默认存在的,这样最终结果是,自动生成了一个包含4个Sheet的WorkBook。   (2)获取Sheet对象并操作单元格: '创建Excel自动化模型对象 Set excelApp = CreateObject("excel.application") '设置为可见模式 excelApp.Visible = true '打开WorkBook excelapp.Workbooks.Open "d:\1.xls" '为1.xls新增一个Sheet Set oSheet = excelapp.Sheets.Item(1) '重命名Sheet为"helloworld" oSheet.name = "helloworld" '为第一行第一列单元格赋值"QuickTest" oSheet.cells(1,1) = "QuickTest" '保存当前WorkBook excelApp.ActiveWorkbook.Save '释放Excel对象 Set excelApp = nothing   执行结果:   还是一样打开1.xls文件之后,获取第一个Sheet对象,并对其第一行第一列的单元格进行赋值,并在最后进行WorkBook的保存操作,最终释放所有对象。   注意:此处需要注意的是,当执行了“Set oSheet = excelapp.Sheets.Item(1)”这段脚本时,脚本已经成功获取到了Sheet1对象的引用,当在VbsEdit中操作此对象时,会发现智能提示并没有开启,也就是说Cells方法是点不出来的,需要手工输入才行。而Sheet1对象也根本就没有被加载到VBSedit中,其实这是因为此时Sheet1还没有被实例化,因此无法获取到Sheet1的任何方法和属性,解决方法就是Excel Macros。当打开Excel后,用快捷键Alt+F11进入到Excel自带的Visual Basic 编辑器,可以看到左边的工程视图里有3个初始化Sheet对象以及当前的WorkBook对象,如图3-32所示。 图3-32   如图3-32所示,可以看到在Excel的Visual Basic编辑器中的工程视图,可以找到已经实例化的Sheet1对象,这样可以直接去调用它的所有方法和属性。并且在图3-32的右边可以清楚地看到Sheet1,在输入点后会弹出相应的方法和属性。同时选中Sheet1后输入快捷键F2,可以直接查看Sheet1对象的所有方法,如图3-33所示。 图3-33   如图3-33所示,在对象浏览器中已经加载了Sheet1对象,并且可以看到其对象下的所有方法和属性。所以,在编辑区才可以成功进行代码提示。这里就是告诉读者,使用VbSEdit写EOM时,代码提示只能到达Sheet层对象,如果不是很熟悉的话,完全可以不用去背,直接打开Excel,通过对象浏览器进行查找对应的方法,或者直接在Excel编辑器里写好再复制粘帖出来也是一个不错的方法。更简单些就是,VBSEdit中调用EOM下的Sheet层对象,就是Excel宏中的Sheet对象,前者无代码提示,后者有代码提示。   总结:   在这一小节中,通过一个最简单的例子让读者了解了,如何利用EOM自动化模型来对WorkBook以及WorkSheet进行自动化操作,包括它们两者之间的区别,读者也可在这些例子的基础上进行完善,并形成自己的方法来应用到项目中。 3.4.2  动态加载并运行宏代码   稍微有点Excel基础的应该都会知道Excel本身有一个相当强大的功能,那就是宏,在英文版里称为“Macro”,其实不止Excel中有,Office中的几乎所有产品都带有宏这个功能,宏的作用就是可以让用户自行定义一些操作,并且通过计算机进行自动化,省去用户很多繁琐重复的操作。为什么要在此处提到宏的概念呢,看一下实例就明白了。   (1)沿用上一小节,打开D盘中的1.xls文件,打开后直接按下快捷键Alt + F11,打开宏代码编辑器,并双击左边的Sheet1打开Sheet1库,并在右边的脚本区中输入如图3-34所示的tt函数。 图3-34   (2)在QTP中输入以下脚本并执。 '创建EOM对象 Set eom = CreateObject("excel.application") '打开D盘中的1.xls eom.Workbooks.Open("d:\1.xls") '获取到Sheet1对象 Set sheet1 = eom.Sheets.Item(1) '****执行Sheet1下的tt方法***** Sheet1.tt ‘****************************** '关闭当前工作簿 eom.ActiveWorkbook.Close '退出Excel应用 eom.Quit '释放 Set eom = Nothing   (3)最终执行结果显示test的消息框,如图3-35所示。 图3-35   分析:   脚本的实现很简单,就是创建EOM对象,获取到Sheet1对象后,并调用Sheet1对象下的tt方法,关闭工作簿后退出应用。这里是想让读者学会此种用法,并且让读者知道,在Excel的宏下的所有方法函数都是可以通过QTP结合VBS来直接调用的。  重点解析:   通过在Excel中写脚本让QTP调用,实际上此功能有点类似于在Visual Studio中开发相应的库之后生成DLL,供QTP进行调用。当然Excel的宏虽然没有Visual Studio那么强大,但是应对自动化测试也已经绰绰有余了。但是为什么这里还要另外提出来呢,既然有了Visual Studio开发DLL库,那此方法不是就变得多余了吗?说到这里,我想有过调用DLL经验的QTP自动化测试人员应该知道,每个需要调用的DLL在使用之前都是需要进行注册(除了在生成DLL的本机上),同样在Excel中会发现,它不需要注册,也不需要任何的其他预备操作即可进行直接使用(相信大多测试机上是安装Office的),并且它还有一个更加强大的优势:动态地生成宏,并利用QTP进行调用,而DLL这样的加密文件是不可能做到的。既然是动态生成,那么不止是宏代码,甚至是Excel同样也需要动态,这样才是真正的动态,实现思路就是新建一个Excel和WorkBook,动态加载宏后,运行宏的函数库中的方法后,不留任何痕迹的情况下关闭Excel,最终达到全部动态生成的效果。接下来看一下如何才能实现动态方式。   1.字符串加载方式   打开QTP,并在其编辑区输入如下脚本: '创建EOM对象 Set eom = CreateObject("excel.application") '打开D盘中的1.xls文件 Set oWorkbook = eom.Workbooks.Add '进入到宏编辑区并获取到WorkBook的脚本模块对象 Set oModule = oWorkbook.VBProject.VBComponents.Item("ThisWorkbook") '定义动态脚本 Dim newCode '所有脚本内容 newCode = "Function tt()" +vbNewLine newCode = newCode + "msgbox ""test"""+ vbNewLine newCode = newCode + "End Function"+ vbNewLine '加载脚本 oModule.CodeModule.AddFromString newCode '执行脚本 oWorkbook.tt '关闭WorkBook,此处false代表不保存直接关闭 oWorkbook.Close false '退出Excel应用 eom.Quit '释放 Set eom = Nothing   分析:   创建EOM对象,添加一个WorkBook,获取到VBProject下的VBComponents中WorkBook的编辑区对象,并使用codeModule对象添加需要的代码,接着就可以直接执行方法函数,最后关闭WorkBook。注意这里close方法需要添加一个false参数,否则Excel会自动跳出是否保存的界面,导致脚本执行失败,此处false代表不保存直接关闭。   2.文本文件加载方式   前面是通过在运行代码中写入字符串实时加载需要的函数,称为字符串加载方式,接下来介绍一种更加实用的方式—文本文件加载方式,此方式可以直接把所有的函数放在一个BAS文件中,然后直接导入到Excel宏之中并运行。   首先需要创建一个BAS文件,此格式为Excel导出的宏文件格式。在这个BAS文件中加入需要的一些函数。此处就把前面的tt函数加入到BAS中,并且保存到D盘中,在真实项目中,建议使用相对路径,这样方便以后的移植。准备工作完成之后,就可以正式开始了。 '创建EOM对象 Set eom = CreateObject("excel.application") '打开D盘中的1.xls文件 Set oWorkbook = eom.Workbooks.Add '进入到宏编辑区并获取到WorkBook的脚本模块对象 Set oModule = oWorkbook.VBProject.VBComponents.Item("ThisWorkbook") ' 直接动态导入函数库文件 oModule.CodeModule.AddFromFile "d:\1.bas" '执行脚本 oWorkbook.tt '关闭WorkBook,此处false代表不保存直接关闭 oWorkbook.Close false '退出Excel应用 eom.Quit '释放 Set eom = Nothing   分析:   调用VBProject模块并导入1.bas文件后,此处需要注意,如果直接执行以上脚本,会在执行到调用VBProject这一行出错,如图3-36所示。 图3-36   这是因为Excel考虑到安全性的问题,默认是限制用户使用脚本直接访问VBProject程序,默认状态是没有设置为信任,因此把它的状态改为可信任状态即可。当打开Excel后,依次进入工具→宏→安全性,选择“可靠发行商”选项卡后,如图3-37所示。 图3-37   在红色边框区域可以看到“信任对于‘Visual Basic项目’的访问”,默认状态下Excel是不会对其进行信任的,把此选项选上。即可做到对VBProject信任状态了。说到这里有些读者会问:既然是全部动态的,那么出了这个问题还一定要手工干预才能够解决问题,那不是非常的被动吗,只要移植到其他计算机上就必须要在此处设置一下才行。在此告诉各位读者,其实完全不用在Excel中进行手工设置即可解决此问题。首先可以想一下,很多程序或者软件它的预设置都是存放在注册表中的,因此,只需要直接修改注册表即可,那要自动化注册表就很简单了,直接使用WSH对象即可轻松完成。接下来重新修改了脚本: '创建WSH对象 Set oWshShell = CreateObject("wscript.shell") '创建EOM对象 Set eom = CreateObject("excel.application") '写入注册表,设置对VBProject进行信任 oWshShell.RegWrite _ "HKLM\SOFTWARE\Microsoft\Office\11.0\Excel\Security\AccessVBOM" _ , 1, "REG_DWORD" '打开D盘中的1.xls文件 Set oWorkbook = eom.Workbooks.Add '进入到宏编辑区,并获取到WorkBook的脚本模块对象 Set oModule = oWorkbook.VBProject.VBComponents.Item("ThisWorkbook") ' 直接动态导入函数库文件 oModule.CodeModule.AddFromFile "d:\1.bas" '执行脚本 oWorkbook.tt '关闭WorkBook,此处false代表不保存直接关闭 oWorkbook.Close false '退出Excel应用 eom.Quit '删除注册表 oWshShell.RegDelete _ "HKLM\SOFTWARE\Microsoft\Office\11.0\Excel\Security\AccessVBOM" '释放 Set oWshShell = Nothing Set eom = Nothing   分析:   以上脚本和前面的脚本差别不是很大,只是在最开始和最后的地方加入了自动写和还原注册表。“HKLM\SOFTWARE\Microsoft\Office\11.0\Excel\Security\AccessVBOM”此路径的键值就是控制Excel自动连接VBProject的信任开关。在打开了此信任开关之后,脚本就可以顺利执行完毕。但是有一点需要注意,在最后需要把加入的键值删除掉来保证下一次执行时写入注册表不会出现系统错误。 3.4.3  Excel使你可以实现更多 3.4.3.1  利用ADO打造Excel数据库   ADO是一个可以自动化数据操作的COM组建,这点相信大多数自动化测试工程师都不会陌生,在数据验证方面,ADO是一个利器。但ADO在对Excel做数据验证时同样也具有相当不错的效果。接下来就来看一下具体如何使用ADO来对Excel进行数据验证。   首先在D盘新建一个Excel数据文件,并按照图3-38所示的格式输入数据。 图3-38   这里需要注意一下,如果需要使用ADO数据源方式来操作Excel,就必须按照图中的格式,ADO默认Excel的每个Sheet为一个表,而每个Sheet的第一行即为字段名,从第二行开始即为字段值。每个字段值会根据当前的列与字段名一一对应。如果读者把此Excel文件导入到QTP的DataTable中,就会发现一个细节,DataTable读取Excel的方式和ADO数据源读取Excel方式的格式是相同的,导入之后,DataTable自动把第一行数据作为字段名来处理,如图3-39所示。 图3-39   在上一章ADO数据库操作中已经介绍了ADO的使用步骤,接下来就按照前面的步骤来进行。首先需要对ADO数据库操作,肯定是需要进行数据库连接,而需要连接数据库就必须要先获取到数据库连接字符串,获取到连接字符串的方式一共有3种,具体使用哪一种读者可自行挑选,此处就直接给出连接字符串了:"DSN=Excel Files;DBQ=D:\1.xls;"。   DSN后面的值为数据源名称,DBQ代表需要连接的Excel所在的路径。获得了字符串后,接下来就是编写SQL语句,这里给出一个最常用的例子:"select * from [Sheet1$]",前面提到过Excel的每个Sheet即为SQL的表名,但是在Excel中需要注意的是,SQL语句中如果需要描述表名,需要在后面添加一个“$”符号才能识别,读者要注意这一点,不要遗漏了。准备 工作都已经完成了,下面就来看一下具体是如何操作的: '定义SQL连接字符串 sqlConnection = "DSN=Excel Files;DBQ=D:\1.xls;" '定义SQL语句 sqlCommand = "select * from [Sheet1$]" '创建ADO对象 Set oExcel = CreateObject("adodb.connection") '打开数据连接 oExcel.Open sqlConnection '执行SQL语句并获得查询结果集 Set oRst = oExcel.Execute(sqlCommand) '获取username字段名对应的字段值 MsgBox oRst.Fields("username") '关闭数据库 oExcel.Close '释放资源 Set oRst = Nothing Set oExcel = Nothing   执行结果如图3-40所示。 图3-40   成功获取到username字段名对应的字段值,通过这种方式来管理数据源是一种相当高效的手段,在下一小节会详细讲解,如果使用ADO与数据字典结合来完成一种更加高效的数据驱动方式。 3.4.3.2  高效数据字典的实现   QTP自带有DataTable,相信大多数自动化测试工程师都会使用DataTable来作为数据驱动测试,它与QTP结合的非常好,并且使用起来也非常简单。但是由于QTP源代码是封闭的,因此功能上存在着一定的局限性使得我们无法对其进行扩展或者更改。但如果使用Dictionary字典对象与ADO结合就可以打造一个完美、高效、灵活的外部自定义DataTable,并且可以自己进行任意的扩展。   前一小节已经介绍了怎样使用数据源ADO来控制Excel的数据,在控制数据方面上,其优势比EOM明显方便很多。接下来就来尝试一下使用数据源与Dictionary对象结合一起的操作。这里仍然延续使用上一小节D盘中的“1.xls”,并在其中多加两列属性和对应的数据,如图3-41所示。 图3-41   接着定义一个数据类,用于QTP直接来调用它,方便以后的使用,脚本如下所示: Class oDataDic          Private oDic         '定义Dictionary对象          Public oWorkBookPath     '定义WorkBook路径变量          Public oSheetName        '定义Sheet名称          Public oRowNo         '定义包含数据的Excel行数          '====================================================          '载入Excel          Public Default Function Load(oWorkBookPath, oSheetName, oRowNo)                   With Me                            .oWorkBookPath = oWorkBookPath                            .oSheetName = oSheetName                            .oRowNo = oRowNo                   End With                     BuildContext                   Set Load = oDict          End Function   '=======================================================          Private Function BuildContext                   Dim oConn, oRS, arrData, x                     Set oConn = CreateObject("ADODB.Connection")                   Set oRS = CreateObject("ADODB.RecordSet")                     '打开数据源                   oConn.Open "Provider=Microsoft.Jet.OLEDB.4.0;" &_                                            "Data Source=" & Me.oWorkBookPath & ";" & _                                            "Extended Properties=""Excel 8.0;HDR=Yes;"";"                     '查询语句                   sQuery = "Select * From [" & Me.oSheetName & "$]"                     '执行查询                   oRS.Open sQuery, oConn, 3, 3, 1                     '移动数据库游标                   For x = 2 to oRowNo - 1 : oRS.MoveNext : Next                   '创建Dictionary对象                   oDict = CreateObject("Scripting.Dictionary")                     '使用For循环重构Dictionary对象                   For x = 0 to oRS.Fields.Count - 1                            With oDict                                     .Add "" & oRS(x).Name, "" & oRS.Fields(x)                            End With                   Next          End Function            Private Property Let oDict(ByVal Val)                   Set oDic = Val          End Property          Private Property Get oDict()                   Set oDict = oDic          End Property End Class '创建oDataDic类 Set mDataContext = New oDataDic   分析:   此脚本内容比较多,这里一步步解释一下。在类的最上方是一些变量定义,这些都是必备的一些变量,包括路径、Sheet名、行数,并且这些变量都使用了let 和set方法进行了封装,以方便之后的调用。那么接下来就是Load函数,此函数的作用就是载入这些变量,并执行BuildContext方法,其实在理解了之前讲解的ADO连接Excel的技术后,理解此函数应该没有什么大的问题,它的功能主要是连接数据库获取到数据结果集,并把数据集存储到Dictionary对象中。   在了解了oDataDic的这个核心类之后,接着就可以让它发挥应有的作用了。创建一个txt文件,写入以上脚本后保存。然后在QTP下的Setting→Resources中载入此类库,如图3-42所示。 图3-42   载入之后就可以在QTP中进行使用了,为了能够连接Excel进行交互,因此需要定义Excel文件的路径、Sheet名称以及行数,接着就可以顺利载入数据和Excel,投入使用了。 oWorkBookPath = "d:\1.xls" '工作簿路径 oSheetName = "Sheet1" 'Sheet名称' oRowNo = 3 '需要定位的行数 '载入Excel和所有变量 Set mDict = mDataContext.Load(oWorkBookPath, oSheetName, oRowNo) MsgBox mDict("Age") '获取年龄 MsgBox mDict("Sex") '获取性别 MsgBox mDict("Username")'获取用户名 MsgBox mDict("Password") '获取密码  分析:   最开始定义完所有变量后,即可直接使用类库中定义过的mDataContext变量,此变量是oDataDic类的一个实例,因此可以直接调用此类中的所有方法,载入Load函数后,由于Load函数返回的是一个数据结果字典对象,因此,直接使用此对象即可获取所有的Excel中的数据。在QTP中运行以上脚本之后即可获取到需要的最终结果。 3.4.4  总结   本章主要介绍了自动化测试过程中的必备利器Excel,运用EOM自动化Excel的技术,不管是数据驱动还是自动生成Excel报表都是必不可少的,而动态加载宏作为一种较为先进的技术为我们带来了更高的效率。最后一小节的Excel与字典对象的结合又充分发挥出了其高效的数据驱动的模式。其实还有很多的技术可以通过与Excel进行结合,只要能够想象就能实现更多,并最终把这些技术运用到自动化测试项目中。   知识点巩固和举一反三练习   一、沿用本章节3.4.3讲解过程中的文件1.xls,对其进行修改操作   要求1:使用EOM方式进行修改。   要求2:直接使用D盘中的1.xls文件。   要求3:修改的属性为username等于QuickTest的Password属性,把值修改为helloworld。   二、完成和练习一相同的操作   要求1:使用ADO方式进行修改。   要求2:修改的属性为username等于QuickTest的Password属性,把值修改为456456。 3.5  WshShell对象常用方法介绍   阶段要点   ● WshShell对象的简单介绍。   ● 激活窗口AppActivate。   ● 获取当前路径CurrentDirectory。   ● 最常用的sendkeys发送。   ● 注册表信息的操作。 3.5.1  WshShell对象介绍   这一节主要讲一下WSH对象的一些应用,在QTP项目实践中经常会遇到一些与Windows连接比较紧密的操作,这些操作如果使用QTP来完成可能并不是最佳的方案,甚至可能QTP就根本无法完成那么此时可以使用WSH对象来帮助解决此类问题,如最常见的就是读写注册表、运行(command)、发送键盘(sendkeys)等。以上这些操作步骤如果通过WshShell对象来操作即可以轻松完成。下面了解一下什么是WshShell对象。   1.Wscript对象:Windows Script Host object model   首先了解Wscript根对象,简单地说,Wscript对象就是一个Windows脚本宿主对象模型,而Wscript是一个基于Windows脚本宿主对象的根对象。利用它可以创建两种COM对象:WshShell和WshNetWork。   2.WshShell(Wscript.Shell)   可以使用此对象访问Windows Shell的一些应用,主要用于运行本地程序、操作注册表程序、创建快捷键、处理环境变量、发送键盘符等。   (1)实例: '创建WshShell对象 Set WshShell = CreateObject("WScript.Shell") '运行notepad命令,启动记事本 WshShell.Run "notepad" '释放WshShell对象 Set WshShell = Nothing   分析:   脚本是利用WshShell对象运行Windows命令,启动记事本的操作。这里只是一个最简单的应用,下一节开始会重点讲解COM对象在自动化测试过程中的应用。   (2)WshNetWork (Wscript.NetWork)。   可以使用此对象访问局域网环境下的共享资源,主要用于连接网络共享、网络打印机、访问局域网中用户的信息。 '创建WshNetWork对象 Set WshNetwork = CreateObject("WScript.Network") '打印计算机名 MsgBox WshNetwork.ComputerName '打印用户所在的域名 MsgBox wshnetwork.UserDomain '打印用户名 MsgBox wshnetwork.UserName   分析:   此处WshNetWork主要作用是,在局域网中获取用户的一些资源信息。这里就简单介绍一下。在自动化测试过程应用比较少,本节主要还是以WshShell的讲解为主。 3.5.2  激活窗口AppActivat   窗口的激活经常会在自动化测试过程中被使用到,特别是在需要同步等待测试对象时,有时页面需要一定的切换时间,往往很多新手会认为,加适当的等待时间即可解决此问题,其实这样的想法绝对是错误的,因为测试机本身可能会出现不稳定的情况,举个例子,当一台测试机运行时间比较长了,或者此测试机正在做可靠性测试需要执行上千次脚本,那么此时用wait是比较危险的,其关键原因是因为,任何时候切换页面的间隔时间都是不等的,如果wait时间设置短了,可能由于计算机运行的次数太多,内存积攒多导致切换时间超过了wait时间,而如果wait时间太长,会浪费太多的时间。不管哪种方式都不是一个非常可取的办法,如果加入循环等待页面切换直到切换成功,这样便可更好地提高代码的执行效率。因此,循环判断页面切换是否成功就成了关键,WshShell对象就提供了这样一个非常有用的功能。   ● 语法: object.AppActivate title   ● 参数:   Title为需要激活窗口的title属性值,也就是标题或者是Process ID。   ● 返回值:   布尔类型,如果当前窗口被激活,返回True,反之返回False。   实例:   完成一个打开记事本,循环判断其是否被激活,一旦窗口被激活成功之后即输入 字符串“zzxxbb112”。 '创建WSH对象 Set oWshShell = CreateObject("wscript.shell") '启动notepad记事本应用 oWshShell.Run "notepad" '循环等待记事本页面是否被激活,没有激活就一直循环直到激活为止 While Not oWshShell.AppActivate("无标题 - 记事本") : Wend '发送字符串zzxxbb112 oWshShell.SendKeys "zzxxbb112" '释放对象 Set oWshShell = Nothing   分析:   执行以上脚本后可以看到,脚本自动启动了记事本后,在记事本中输入了zzxxbb112字符串,看似非常简单,其实对于新手来说还是比较容易出错,如果此处没有“While Not oWshShell.AppActivate("无标题?-?记事本") : Wend”这一步骤,zzxxbb112可能就无法输入到被打开的记事本中去,接下来我们就仔细探究竟。   比较两个脚本的运行结果。   (1)脚本1: '创建WSH对象 Set oWshShell = CreateObject("wscript.shell") '启动notepad记事本应用 oWshShell.Run "notepad" '激活记事本窗口 oWshShell.AppActivate("无标题 - 记事本") '提示消息框 Msgbox “suc” '释放对象 Set oWshShell = Nothing  (2)脚本2: '创建WSH对象 Set oWshShell = CreateObject("wscript.shell") '启动notepad记事本应用 oWshShell.Run "notepad" '循环等待记事本激活 While Not oWshShell.AppActivate("无标题 - 记事本") : Wend '提示消息框 Msgbox “suc” '释放对象 Set oWshShell = Nothing   脚本1  执行结果如图3-43所示。 图3-43   脚本2  执行结果如图3-44所示。 图3-44   分析:   这个例子非常经典,在对比了这两脚本执行结果之后应该很明显地看到,第一个是在窗口激活之前就弹出消息框了,由于记事本挡住了,因此这里看不出。虽然已经在脚本中加入了AppActivate方法来激活记事本窗口了,但是由于脚本执行速度比激活窗口快很多倍,因此脚本执行激活窗口时,往往是还没来得及等窗口激活,就已经执行了下一句弹出消息框脚本了,就会出现不想看到的局面。但脚本2就很好地避免了这样的问题,通过循环遍历了对象的激活状态,同步窗口的激活状态,一旦窗口激活后才弹出消息框,这样的做法是最安全的。可以在图3-43中明显地看到消息弹出框在记事本前面,很明显此处的消息弹出框是在记事本窗口激活后才弹出的。 3.5.3  获取当前路径CurrentDirectory   对于自动化测试脚本开发来说,相对路径也是一个比较常用的技术,不管是对于脚本移植,还是在自动化测试框架中都有着非常重要的意义。在WshShell中就有这样一个获取相对路径的方法,虽然知道在QTP中可以直接在folder里设置相对文件夹路径,但是本人认为,在一些特殊应用中,如在自己搭建框架时,或者需要使用脚本来获取相对路径时,再或者在纯VBS环境下等,这些都是QTP无法做到的。除此之外,利用WshShell也是非常方便的。(后续章节会讲解如何使用fso的getfile方法来获取文件的相对路径)   ● 语法: object.CurrentDirectory   ● 参数:   无。   ● 返回值:   布尔类型,如果当前窗口被激活,返回True,反之则返回False。   实例:实现相对路径。   首先建立一个文件夹,在文件夹下同级目录建立两个VBS,一个命名为main.vbs,另一个命名为msg.vbs,在msg.vbs中输入一个msgbox“zzxxbb112”并保存,如图3-45所示。 图3-45   接着在main.vbs中输入以下脚本: '创建WshShell对象 Set wshShell = CreateObject("wscript.shell") '获取相对文件夹 RelativeFolder = wshShell.CurrentDirectory '拼装需要执行文件的相对路径 msgVbsPath = RelativeFolder + "\msg.vbs" '运行msg.Vbs wshShell.Run msgVbsPath '释放对象 Set WshShell = Nothing   分析:   此脚本首先是调用了currentDirectory方法,获取了当前执行VBS文件的父文件夹,接着拼接需要执行的vbs文件路径后,执行此msg.vbs文件。在完成了以上脚本后,其实就已经实现了路径参数化,也就是相对路径。可以直接复制父文件夹到任何路径下来执行Main.vbs。但是注意,如果路径中间出现的文件夹名存在空格,这种情况下会出现路径找不到的情况,例如,类似这样的路径“C:\Documents and Settings\Administrator\桌面\zzxxbb112 ”,因为WshShell.run方法必须保证路径中的文件夹名字不含有空格字符,否则就会报类似这样的错误,如图3-46所示。 图3-46   如果要解决这问题,必须避免空格的直接输入,解决方案为。   ● 引号转化字符串。   办法其实很简单,由于此处不能直接输入空格,可以使用字符串形式,这样run命令就会把此路径作为一个整体,这样就顺利解决了问题,来看一下脚本: '创建WshShell对象 Set wshShell = CreateObject("wscript.shell") '获取相对文件夹路径 RelativeFolder = wshShell.CurrentDirectory '拼接相对路径 msgPath = RelativeFolder + "\msg.vbs" '加入引号转化相对路径为字符串 chrMsgPath = Chr(34) & msgPath & Chr(34) '运行相对路径下的main.vbs wshShell.Run chrMsgPath '释放对象 Set wshShell = Nothing   分析:   此处脚本只是加入了chr(34),由于脚本中需要加入引号时不可以直接添加,因此,VBS特别设立了一些特殊符号的输入,chr(34)就是其中一个,表示为引号,这样就可以把相对路径成功转化为字符串方式来运行,也就不会报错了。 3.5.4  最常用的sendkeys发送   这一小节是本章的重点,虽然比较简单,但在自动化测试中往往能够解决很多难题,例如,当对象无法识别但是又需要在此对象中输入字符串时,即可先想办法点击此对象,然后使用sendkeys发送键值,又或者当遇到需要使用快捷键时,同样也可以使用sendkeys方法,当然它也支持一些特殊键,如alt、ctrl、shift等。首先来看一下sendkeys的语法。   ● 语法: object.SendKeys(string)   ● 参数:   String表示需要发送的字符串,以及一些特殊键盘。   ● 返回值:无。   实例1:模拟输入字符串。 '创建WSH对象 Set oWshShell = CreateObject("wscript.shell") '启动notepad记事本应用 oWshShell.Run "notepad" '循环等待记事本页面是否被激活,没有激活就一直循环直到激活为止 While Not oWshShell.AppActivate("无标题 - 记事本") : Wend '发送字符串:”zzxxbb112” oWshShell.SendKeys "zzxxbb112" '发送回车键:{enter} oWshShell.SendKeys "{enter}" '发送字符串“QuickTest” oWshShell.SendKeys "QuickTest" '释放对象 Set oWshShell = Nothing   分析:   执行完脚本后,可以看到打开了笔记本后,输入zzxxbb112、输入回车、输入QuickTest,此处{enter}代表回车符。   注意:不存在{Space}键,如果需要输入空格键,可以直接使用“ ”。  实例2:模拟特殊键。   此处特别说明一下几个常用的特殊键:CTRL、SHIFT、ALT这3个常用组合键直接使用+、^、%,其余的除字母键外的功能键,如{Esc}、{Enter}。 键 名 参 数 SHIFT + CTRL ^ ALT %   表3-1中的键名大多是通过组合键来使用,来看如下脚本: '创建WshShell对象 Set wshShell = CreateObject("wscript.shell") '发送Ctrl+Shift + Esc组合键 wshShell.SendKeys("^+{Esc}") '释放WshShell对象 Set wshShell = Nothing   分析:   执行完以上脚本后,会自动弹出任务管理器。此处的“^+{Esc}”代表输入了组合键Ctrl+Shift+Esc调出了任务管理器,但是这里需要注意,使用sendkeys发送Ctrl+Alt+Del无法调出任务管理器。 3.5.5  轻松完成注册表的读写   在自动化过程中,某些特殊的情况下,可能会使用到注册表的操作,如需要加入启动项的操作,那么需要进入“HKLM\Software\Microsoft\Windows\CurrentVersion\Run”添加一个启动键值。此时WshShell就发挥作用了,它提供了3种方法供我们对注册表的操作:增加、读取、删除。 3.5.5.1  注册表信息增加   ● 语法: object.RegWrite(strName, anyValue [,strType])   ● 参数:   StrName:键的路径名。   anyValue:键的value值。   strType:键的类型。   ● 返回值:无。   实例:在启动项中增加一项。 '创建WshShell对象 Set wshShell = CreateObject("wscript.shell") '初始化注册表路径 regPath = "HKLM\Software\Microsoft\Windows\CurrentVersion\Run\"'写注册表信息 wshShell.RegWrite regPath+"zzxxbb112","d:\1.vbs","REG_SZ" '释放对象 Set wshShell = Nothing   分析:   执行以上脚本后,可以在路径“HKLM\Software\Microsoft\Windows\CurrentVersion\Run\”下找到新建的一个键zzxxbb112,它的value值为d:\1.vbs ,类型为REG_SZ,此路径即为Windows的启动项注册表路径。可以在此处随意添加自己想要的开机启动项,当执行了RegWrite方法后,注册表就会自动把zzxxbb112这个键注入到启动项里,如图3-47所示。   如图3-47所示,可以看到在Run路径下有很多的启动项,此处都是之前装软件时有些软件自动注入的,箭头指的地方就是刚才添加进去的一个字符串值,可以清楚地看到名字为zzxxbb112,值为“d:\1.vbs”,一旦此值被添加后,在重新启动Windows时即会自动运行d:\1.vbs,对于间断性重启的自动化测试来说,此方法是一个非常有用的。 3.5.5.2  注册表信息读取   ● 语法: object.RegRead(strName) 图3-47   ● 参数。   键的路径名。   ● 返回值:   键的value值。  实例:读取启动项中键的value值。 '创建WshShell对象 Set wshShell = CreateObject("wscript.shell") '初始化注册表路径 regPath = "HKLM\Software\Microsoft\Windows\CurrentVersion\Run\"'打印启动项中键名为zxxbb112的键值 MsgBox wshShell.RegRead(regPath+"zzxxbb112") '释放对象 Set wshShell = Nothing   分析:   此处还是对启动项路径进行操作,使用RegRead方法获取键名为zzxxbb112的键值,此方法同时也可以用作验证之前添加的值是否正确。执行结果如图3-48所示。 图3-48 3.5.5.3  注册表信息删除   ● 语法: object.RegDelete(strName)   ● 参数。   strName:键的路径名。   ● 返回值:   无。   实例:读取启动项中键的value值。 '创建WshShell对象 Set wshShell = CreateObject("wscript.shell") '初始化注册表路径 regPath = "HKLM\Software\Microsoft\Windows\CurrentVersion\Run\"'删除键zzxxbb112 wshShell.RegDelete(regPath+"zzxxbb112") '释放对象 Set wshShell = Nothing   分析:   当在完成间断性持续重启自动化测试时,在最后必须要删除先前增加的启动项来还原测试环境,因此,可以使用RegDelete方法来进行键的删除。当然删除时必须要保证键已经存在,否则会出现如图3-49所示的错误。 图3-49 3.5.6  总结   本节主要介绍了WshShell在自动化测试中的一些常用方法,这些方法的应用都能极大地简化自动化测试过程中的一些操作。除了这些常用的方法以外,它还存在着很多的方法,本节就不一一介绍和讲解了,关于更多WSH的方法和介绍可以参见微软的MSDN在线帮助。   注:本节不设置习题。 3.9  API的应用   阶段要点   ● 透彻分析Extern对象实际用法。   ● 区分3种DLL调用方式。   ● 快速掌握如何把原始VB API转化为QTP API。   ● 如何通过VBS调用API实现简单GUI自动化操作。 3.9.1  Extern对象详解   做过Windows程序开发的工程师们一定会对WIN32 API有所了解,它主要是Windows提供给开发人员的一个应用程序的接口,提供了很多方法,包括获取鼠标信息、修改窗口大小、修改控件颜色、给控件发送消息等。由于在VBScript中并没有提供这样一个可以调用API的方法,因此MERCURY在QTP中加入了一个可以调用API的接口对象,它就是Extern保留对象。下面就详细介绍一下如果使用此对象。   Extern对象:用于调用Win32 API的保留对象   类方法:Declare:声明DLL内部的过程引用。   调用方法:Extern.Declare (RetType, MethodName, LibName, Alias [, ArgType(s)])   需要使用API接口首先就必须在QTP中声明,一旦成功声明了一个内部函数引用后,QTP就会自动加载此方法,并且在调用Extern之后提供了很方便的Intellisence。   这里需要对此函数的几个参数进行一下解释,第一个RetType是需要调用函数返回值的数据类型,MethodName就是函数名,这个从字面上就能够看出来,LibName则是需要调用的DLL库文件的路径,如果是Windows系统,DLL则直接引用DLL的文件名即可,如果是自定义,则需要写入完整路径才可以进行调用。Alias则是DLL中的方法名,如果函数需要参数,可以在最后一并加上。具体调用方式如图3-110所示。 图3-110   如图3-110所示,第一行代码的作用是申明需要调用的方法和DLL,此处调用的是user32.dll中的GetForegroundWindow的方法,声明完毕之后,可以看见Extern对象实例已自动加载了之前声明的方法到intellisense,即可直接通过Extern保留对象点出刚才直接声明的方法。   注意:此处的Extern有一个缺陷,就是intellisense的更新问题,当声明好一个API方法之后,QTP首先需要运行一次才会为Extern保留对象自动添加这个声明好的变量。如果删除声明的这一行脚本,QTP的intellisense中仍然会保留原先的API方法,而不会自动删除。   API的用法和一些注意事项都已经说的很清楚了,下面就通过几个实际的例子讲一下如何来使用API。   实例1:获取当前窗口的句柄。   句柄是一个在自动化测试中经常会使用到的、能够唯一识别控件的属性。但是句柄往往是动态的,每一次重新打开窗口之后,此窗口的句柄就会发生变更,因此,只有通过动态获取到当前打开的窗口句柄来定位对象窗口,下面看一下QTP中具体是如何实现的: '#######启动浏览器######## systemutil.Run "iexplore.exe" '#######创建API Extern对象########## Extern.Declare micHWnd, _ "GetForegroundWindow", "user32.dll", "GetForegroundWindow" '######使用WIN32 API获取窗口的句柄###### hwnd = Extern.GetForegroundWindow() '########通过获取的句柄控制浏览器窗口并跳转百度##### Browser("hwnd:=" & hwnd).Navigate "http://www.baidu.com"   分析:   首先运行了IE浏览器,之后声明WIN32大中的GetForegroundWindow方法,此处的GetForegroundWindow方法代表动态获取当前窗口的句柄,最后使用QTP的描述性编程直接定位Browser对象,并转至百度页面。   实例2:运行测试脚本时最小化QTP窗口。   在执行自动化测试过程中,经常会出现QTP窗口挡住测试程序界面的现象,特别是在进行脚本调试的时候,需要去判断到底问题是出在哪个环节上,如果QTP把界面给挡住了,将会非常不方便,每次都需要使用Tab键来进行切换才可以,不过当有了WIN32 API之后,就可以直接调用它的ShowWindow方法来直接把QTP自身对象窗口最小化: Const SW_MINIMIZE = 6 '声明user32.dll中的ShowWindow方法 Extern.Declare micLong,"ShowWindow","user32.dll","ShowWindow",micLong,micLong '高亮QTP窗口对象 Window("text:=QuickTest.*").highlight '获取QTP自身的窗口对象的句柄属性 ohwnd = Window("text:=QuickTest.*").GetROProperty("HWND") '调用ShowWindow方法 并加入最小化窗口参数 Extern.ShowWindow ohwnd,SW_MINIMIZE '等待两秒钟 wait 2   分析:   代码中的第一行相信读者应该在之前的例子中已经掌握了它的用法。接着脚本通过描述性编程的方式来获取到QTP对象窗口(此处也可以使用对象库,为了讲解方便,直接用描述性编程来代替),并使用GetROProperty方法获取到其QTP对象窗口的句柄属性值,并加入到后面ShowWindow函数的第一个参数中,作用是定位需要更改窗口显示状态的窗口对象,第二个参数则是代表需要更改的窗口状态,此处为SW_MINIMIZE,即代表最小化窗口。   注意:QTP本身并没有加入反HOOK注入,因此,同样可以把QTP自身程序窗口加入到对象库中并对其进行一些操作。以上脚本就是直接通过Window("text:=QuickTest.*")获取到QTP对象窗口的,获取到之后即可方便做操作。   小提示:QTP主要提供了3种DLL的调用方式,这3种调用方式分别为Extern、CreateObject、DotNetFactory,而这3种调用方式都分别对应着不同类型的DLL。首先Extern所调用的DLL为WIN32 API - DLL,Createobject所调用的是ActiveX类型的DLL,这类DLL其实应用的最多,AOM \ EOM等都是此类型的DLL,最后一种.NET Factory是直接调用.Net framework 或者.NET自定义的DLL。这些希望读者能够把它们区分开。 3.9.2  VB API转化QTP API   在前一小节中大致讲解了如何在QTP中调用WIN32 API以及Extern的语法和实际使用。但是如果不熟悉WIN32 API,每次使用WIN32 API之前都需要使用API viewer查找到相应的函数声明语句,找到这些相应的函数声明语句之后还不可以直接进行使用,因为QTP本身的语法只支持VBS,而VBS并不支持直接调用WIN32 API,只能将之前的函数声明语句转化成Extern调用声明语句来适应QTP的使用。其实细心的读者一定会发现它们之前的转化是有一定的规律的。因此,完全可以去实现一个自动化转化的过程,当然在这里要告诉大家一个好消息,印度的QTP大师Tarun Lalwani已经为我们完成了这样一个工作,这样就可以直接拿来使用了。这里通过一个简单的例子来讲解如何一步步实施。   1.下载并安装API Viewer tools   由于.NET程序不像在Visual Studio中有API Viewer 这样的工具,因此,需要自行去下载一个,或者计算机上同时也装有Visual Studio 6.0即可。此处提供一个下载地址:   http://www.activevb.de/rubriken/apiviewer/index-apiviewer.html   2.打开API Viewer并加载相应的库   打开时窗口中的很多控件都是Disable状态,即不可用的,因为此时还没有对程序加载相应的库文件,API Viewer是空的状态,现在加载相应的WIN32库文件,点击File→Open,选择Win32api.apv,如图3-111所示。 图3-111   在选中WIN32api.apv后,点击打开按钮,就会加载相应的库。所有控件的数据都被自动填入并可供选择使用,如图3-112所示。 图3-112  如图3-10所示,已经选中了FindWindow,同时此工具自动生成了相应的VB API脚本。   3.下载并安装VB To QTP API Declaration Converter   在获取了原始API脚本之后,就需要把它转化为QTP调用API的脚本了。首先需要把前面提到的Tarun Lalwani提供的工具下载下来。   下载地址为:http://knowledgeinbox.com/downloads/qtp/vb-to-qtp-api-declaration-converter/   下载并安装完此工具之后,打开主程序,可以看到窗口中有两个TextArea文本域,第一个文本域的作用是用于写入原始VB API ,第二个TextArea文本域是用于转化第一个原始VB API成QTP API以后的结果。了解了此转换器的功能后,只需要直接把刚才获取到的VB API 脚本粘帖到第一个TextArea里,接着直接点击转换按钮Convert to QTP API Declarations即可。这样第二个文本域中就会自动生成了与之前VB API对应的QTP API,如图3-113所示。 图3-113   最后在转化成功之后,就可以把此脚本放到QTP中进行直接调用了。 3.9.3  纯VBS调用API实现简单GUI自动化   在本章的最开始已经提到过了VBS是不可能直接实现调用Win32 API的,因此,QTP才提供了一个Extern接口方法供自动化测试工程师调用。而且,本人其实已经说明了Extern是QTP中的一个保留对象,既然是保留对象,那就一定可以在注册表中找到它创建此COM对象所对应的ProgID,如图3-114所示。 图3-114   从图3-114中可以看见Extern的ProgID值已经被暴露出来了,并且可以通过Icons项来找到相应的库路径:D:\Program Files\HP\QuickTest Professional\bin\MicExternCall.dll,这就可以通过CreateObject创建一个新的Extern实例作为独有的对象了,QTP启动时即调用的保留对象。它具体有什么样的好处呢,一起来看一下,直接在VbsEdit中创建此ActiveX com对象。 '创建Extern对象的会话实例 Set Extern = CreateObject("Mercury.ExternObj")   在写入以上脚本之后,可以在VbsEdit中的Object Browser下找到已经实例化的对象,如图3-115所示。 图3-115   同时在使用此对象时,VbsEdit会自动加载相应的属性,如图3-116所示。 图3-116   注意:如果是第一次在VBS中直接创建此对象,可能会出现问题,需要先注册此DLL或者直接在VbsEdit的References中进行注册,如图3-117所示。 图3-117     如图3-117所示,在VbsEdit中引用需要的库后,工具会自动帮助我们进行注册,注册完毕之后即可直接在VBS中运行调用,包括QTP关闭状态下。一起来看几个简单的例子。   实例1:获取窗口句柄: '定义变量 Const micLong = 3 Const micString = 8 '创建Extern对象的会话实例 Set oExtern = CreateObject("mercury.ExternObj") '声明FindWindow对象 oExtern.Declare micLong,"FindWindow","user32.dll", _ "FindWindowA",micString,micString '获取记事本对象窗口的句柄 hwndWindow = oExtern.FindWindow(vbNUllString,"无标题 - 记事本") '打印记事本返回的句柄值 MsgBox hwndWindow '释放对象 Set oExtern = Nothing   分析:   首先,执行此脚本之前需要打开一个记事本窗口,脚本中所实现的功能很简单,就是获取到记事本窗口的句柄,此处一开始需要定义变量,这和QTP中有所不同,因为在QTP的脚本引擎中,已经默认给这些变量进行了赋值,无需进行变量定义就可以直接使用。在VBS中是不可以直接使用的,如果记不住这些变量分别代表的数值是多少,可以查MSDN,也可以直接在QTP中输入以下脚本即可把这些值给打印下来,如图3-118所示。 图3-118   执行结果如图3-119所示。 图3-119   如图3-119所示,直接把这些值复制过去就可以使用了,后面的步骤,除了创建一个新的Extern对象的会话实例外,其他和在QTP中没有什么太多的区别。   实例2:实现一个按钮的点击操作。   假设有如图3-120所示的一个消息框,如何能够直接通过VBS调用API来点击确定按钮呢,如图3-120所示。 图3-120   请看以下脚本: '定义变量 Const micLong = 3 Const micString = 8 Const micRef = 32768 Const BN_CLICK = 245 '创建Extern对象的会话实例 Set oExtern = CreateObject("Mercury.ExternObj") '*************声明所有API函数方法******************* oExtern.Declare micLong,"FindWindow","user32.dll", _ "FindWindowA",micString,micString oExtern.Declare micLong,"FindWindowEx","user32.dll", _ "FindWindowExA", micLong,micLong,micString,micString oExtern.Declare micLong,"PostMessage","user32.dll", _ "PostMessageA",micLong,micLong,micLong,micRef+micLong oExtern.Declare micLong,"SetActiveWindow","user32.dll", _ "SetActiveWindow",micLong '***************************************************** '返回消息框窗口的句柄 hwndWindow = oExtern.FindWindow(vbNUllString,"hello") '返回确定按钮的句柄 hwndButton = oExtern.FindWindowEx(hwndWindow,0,vbNullString,"确定") '激活消息框窗口 oExtern.SetActiveWindow hwndWindow '发送点击事件消息给确定按钮 oExtern.PostMessage hwndButton,BN_CLICK,0,0 '释放对象 Set oExtern = Nothing   分析:   以上脚本一共定义了4个API函数,分别是FindWindow、FindWindowEx、PostMessage、SetActiveWindow。第一个FindWindow是通过窗口title名来获取到窗口的句柄,FindWindowEx根据句柄和控件名来获取到控件的句柄,第三个函数PostMessage给控件传送消息,最后一个是SetActiveWindow代表激活窗口。脚本的执行过程主要是先返回窗口和控件的句柄,随后激活窗口,并为窗口下的按钮控件加入点击事件,最终释放对象。 3.9.4  总结   本节主要对Extern对象进行了详细讲解,并且介绍了一些较为基础的API函数的使用方法,以及在VBS中直接使用API。其实API在实际的自动化测试中应用的频率并不是非常高。一旦在自动化测试过程中遇到一些比较特殊的问题,或者QTP无法解决的问题导致无法继续自动化测试时,API就能显现出它强大的威力了。举一个简单的例子:当QTP操控Web中的选取文件路径对象时就会卡在Dialog对话框上,一旦QTP激活了file控件并弹出对话框后,QTP就会变成,无法停止,也无法继续进行后续脚本的运行状态。只有手动关闭此对话框才可继续执行后续脚本。此种情况就可以使用本节讲到的API来解决。其实Win32已经提供了非常丰富的API供开发人员进行使用,测试人员还可以利用它来实现更多,只有你想不到,没有你查不到。   知识点巩固和举一反三练习   在QTP环境下利用Win32 api最大化当前打开的浏览器窗口。   要求1:使用Extern对象。   要求2:准备一个最小化的IE浏览器窗口。 4.1  QTP深入探索   阶段要点   ● 访问对象自身接口的特殊渠道。   ● 重载页面后Web对象的重用。   ● ORM对象库自动化模型。   ● 重写Report对象。   ● XML联合XSL输出HTML报表。   ● 创建保留对象的会话实例。   ● AutoItX技术的应用。 4.1.1  访问对象自身接口的特殊渠道   在这里介绍扩展另一种访问对象自身接口的技巧,就是通过QTP封装函数来获取,这个技巧非常实用。   方法:“attribute/自身接口属性名”。   使用这个方法,我们就可以通过QTP封装的方法来访问其自身接口了,而不用再使用object来获取,此方法可以结合很多QTP的方法进行使用,并且可以发挥出很强大的作用。   1.结合GetROProperty使用(见图4-1) 图4-1   获取自身接口属性Start的值: Msgbox Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("wd").GetROProperty("attribute/Start")   结论:使用此方法可以获取自身接口的Start属性。   2.结合CheckProperty使用   验证自身接口属性Start的值是否正确: Browser("百度一下,你就知道").Page("百度一下,你就知道")._ WebEdit("wd").CheckProperty "attribute/Start","fileopen"   结论:使用此方法可以验证自身接口的属性,避免了再使用.object来获取并进行判断的麻烦。   3.结合WaitProperty使用 Browser("百度一下,你就知道").WaitProperty "attribute/ReadyState","4"   结论:我觉得与此方法结合最实用。大家都知道QTP的这3个方法都是QTP封装的方法,这些方法都只能支持QTP封装的属性,而自身接口属性是不能使用此方法进行访问的,这样如果需要使用WaitProperty方法去等待自身属性就会不可实现的,若使用attribute进行转化就可以轻松完成我们的需求。 4.1.4  重写Reporter对象   不知道大家有没有使用过自定义的Reporter,QTP的Reporter应用非常局限,甚至有时感觉设计的不是很合理,其实完全可以写自定义的结果报告,不过本小节不是讲解怎么设计结果报告,而是讲解通过重写QTP中的Reporter对象来完成自定义的结果报告。这样做的优势很明显,其一是让能够熟练使用Reporter对象的读者继续使用他们熟悉的函数,其二是可以充分利用Reporter对象的智能代码提示功能。省去配置自定义保留对象的麻烦: '重写自定义类 Dim Reporter Set Reporter = New clsReporter '定义可以被Action调用的FUNCTION Public Function GetReporter Set GetReporter = Reporter End Function '类定义 Class clsReporter Dim oFileReporter Public Sub ReportEvent(iStatus, sStepName, sDetails) oFileReporter.AppendAllText "D:\hello.txt", _ sStepName & " - " & sDetails & " - " &Now & vbCrLf End Sub Private Sub Class_Initialize Set oFIleReporter = DotNetFactory("System.IO.File") End Sub End Class   分析:   首先介绍一下clsReporter这个类,它主要实现的是,新建一个TXT文件,然后在里边写入内容。里面有一个函数名字为ReportEvent,注意这个函数名是不能改的,因为QTP中Reporter有一个方法也叫ReportEvent,这样才能够重写此函数,在类的最后还有一个初始化类,使用的是.WETFactory这个接口来调用.NET类,这个类中牵涉的知识点还是比较多的,若是不清楚某些基础知识,可到本书前面的章节中进行补习。   注意:   如果需要将此类应用到QTP的Resources中,就必须在此函数文件中定义类的实例化函数,这是因为QTP的Rescources不支持直接读取函数文件中的Class类,因此,需要增加一个步骤让QTP可以访问到函数库中的Class: Set Reporter = New clsReporter '定义可以被Action调用的Function Public Function GetReporter Set GetReporter = Reporter End Function   分析:   创建clsReporter这个类并返回Reporter对象,并通过GetReporter这个函数来把Reporter对象进行返回。接着就可以把以上所有的代码都保存为一个VBS文件,并通过QTP的Associated Function Libraries把此文件加载进来,这样就可以直接用代码来调用此函数,从而达到重写QTP中Reporter的方法的功效。   ● 资源池加载视图(见图4-9)。 图4-9   如图4-9所示,当完成了函数库文件后,即可直接在QTP的Recources中加载此函数库文件进行使用。那么,如何来使用此函数库中的类文件呢,具体使用如下: Dim Reporter Set Reporter = GetReporter   ● 脚本试图代码(见图4-10)。 图4-10   ● 结果文件(见图4-11)。 图4-11   总结   Reporter对象其实还有很多我们不为人知的秘密,有很多值得我们去探索的地方,例如:通过Visual Studoin 8的Debug引擎查到的许多隐藏方法,以及在Reporter载入HTML文件并显示等。 4.2.7  映射无法识别的.NET对象类   在自动化测试过程中,经常会遇到对象无法正常识别为对应插件的封装测试对象,在此类情况中,有时可以采取对象映射的方式来强制把对象改变成插件类型的封装对象。这个步骤也很简单,直接可以在Object Identification下的User-Defined下的object-mapping中,把需要映射的对象加入到此列表中即可,这样QTP如果再次捕获此对象时就会把它强制转化为映射类型的封装对象。但是使用过此功能的读者应该都知道,QTP本身只支持标准Windows类型的对象映射,并不支持.NET类型的对象映射,也就是说无论如何映射,运行的结果也只能是Windows类型的封装对象,无法映射成.NET类型,如图4-46所示。 图4-46   作者发现当使用QTP映射一个无法识别的对象为普通封装对象时,在注册表中会自动生成一条记录。   映射标准封装对象位置:HKEY_USERS→   S-1-5-21-1708537768-2049760794-682003330-500 (可能后面一串字符会和读者的不一样)→   Software→Mercury Interactive→QuickTest Professional→MicTest→Packages→StdPackage→ClassMap下,如图4-47所示。 图4-47   如图4-47中,可以发现一些默认的映射对象以及之前映射的对象记录。左边的方框是无法识别的测试对象类型,右边方框为需要映射的封装测试对象类型。同理,如果在.NET包下也能找到这样一个ClassMap就一定可以直接进行映射了。   映射.NET对象位置:HKEY_USERS→S-1-5-21-1708537768-2049760794-682003330-500 (可能后面一串字符会和读者的不一样)→Software→Mercury Interactive→QuickTest Professional→MicTest→Packages→SwfPackage→ClassMap下,如图4-48所示。 图4-48   分析:   只需要按照图4-48所示的规则添加一行需要映射的记录即可成功映射为.NET对象,有兴趣的读者不妨试一试,此方法也是一种相当实用的技巧。 5.1  QTP设计模式   阶段要点   ● 带你进入设计模式的世界。   ● 单例模式设计。   ● 工厂模式设计。   ● 命令包装模式设计。   ● 回调模式设计。   ● 函数指针模式设计。   ● 类的继承模式。 5.1.1  初识设计模式(Design Patterns)   设计模式这个名称相信对于有过面向对象开发经验的读者来说不佰生,最早是国外的GoF四人帮发表了23个基础的设计模式,并对其进行了规范化,后来被人们广泛应用于程序设计中,设计模式(Design pattern)是一套被反复使用、多数人知道的、经过分类编码目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式使代码编制真正工程化,设计模式是软件工程的基石。   在很多面向对象的语言中,设计模式使用起来非常方便,并且有些模式实现起来相当简单,这是因为底层的代码设计上都已经为这些模式“铺好了路”,而在QTP中,它的脚本编辑器底层语言是VBScript,而此语言本身相比很多面向对象的高级语言来说。具有明显的劣势和差距。大多数同行都认为VBScript自身根本不能使用设计模式,这是它本身存在的缺陷。作者经过多年的研究以及查阅国外的一些技术文献后,得出一个结论:VBScript虽然不能直接实现众多设计模式,但可以间接通过脚本的后期优化来实现。   在本章接下来的内容中将讲解,如何使用VBScript来实现一些常用的代码设计模式,其包括:单例设计模式(Singleton Pattern)、工厂设计模式(Factory Pattern)、命令包装模式(Command Pattern)、回调设计模式(CallBack)、函数指针模式(Function Pointer)、类的继承模式(Class Inheritance)等,相信这一定会成为本书最大亮点。 5.1.2  单例设计模式(Singleton)   从本节开始就为大家介绍,先从单例设计模式开始。试想一下,当一位自动化测试工程师需要实现一个相对复杂的脚本,此脚本又需要连接数据库或者Excel的Sheet表,并且对其进行读写等多项操作,要在VBS cript中实现这些操作,首先需要创建这些COM组件,如Excel.Appliction、WScript.Shell,若在操作流程或者脚本流程比较繁多或者复杂的情况下,就会出现一个COM组件被创建多次的现象,那么怎样才能避免这样的现象呢?这里就引入了VBScript的单例模式应用。   首先来看一个非常简单的例子: Option Explicit Public oExcel '声明全局变量 Dim bAleadyInit '判断对象是否存在的标志位 '检查对象是否已经被初始化 bAlreadyInit = False If IsObject(oExcel) = True Then '对象初始化 If Not oExcel is Nothing Then '激活状态 bAlreadyInit = True End If End If '如果标志位是False,则创建一个实例对象 If bAlreadyInit = False then Set oExcel = CreateObject("Excel.Application") end if '注意:此处的Whatever只是代表任意方法的意思,并没有此方法 oExcel.Whatever   分析:   脚本中首先声明了两个变量,一个为Excel对象变量,另一个为标志位,用于判断Excel是否已经被实例化。接着把标志位设置为False,并判断Excel对象变量是否为一个对象,如果不是一个对象,那么就直接退出,说明没有被实例化,需要新创建一个;如果Excel对象变量是一个对象,继续判断此对象是否已经被释放,如果没有被释放,那么就可以把它设为已实例化状态,并在下次调用时通过判断标志位为已实例化状态而不会重新去创建一个新的实例。虽然此脚本已经完成了所需要的功能,但此脚本调用起来非常繁琐,每次都需要使用这样的重复代码,效率十分低,因此,这里必须使用函数对其进行封装,方便以后重复进行调用: Option Explicit Function GetExcel Public oExcel '声明全局变量 Dim bAleadyInit '判断对象是否存在的标志位 '检查对象是否已经被初始化 bAlreadyInit = False If IsObject(oExcel) = True Then '对象初始化 If Not oExcel is Nothing Then '激活状态 bAlreadyInit = True End If End If '如果标志位是False,则创建一个实例对象 If bAlreadyInit = False then Set oExcel = CreateObject("Excel.Application") end If '将oExcel对象返回给当前函数 Set GetExcel = oExcel End Function Set oExcelApp = GetExcel '获取excel对象 oExcelApp.Whatever '注意:此处的Whatever只是代表任意方法的意思,并没有此方法  分析:   使用以上方法就可以达到永远只有一个实例对象,如果有创建,直接引用已创建的实例,如果没有创建,则直接创建一个,使oExcel 这个全局变量永远只有一个实例。但是这样写程序我们会发现一个问题,当在创建多个对象并将指针指向全局变量的这个实例时,我们都需要销毁这些对象,但是就算把这些对象全部销毁,全局变量的实例还是一直会持续下去,不会释放。因此,需要使用类进行包装来改善这种情况,实现如下所示: Option Explicit Public oExcel '声明全局变量 Class ExcelWrapper Private oExcelObject ‘初始化类 Private Sub Class_Initialize Dim bAleadyInit '判断对象是否存在的标志位 '检查对象是否已经被初始化 bAlreadyInit = False If IsObject(oExcel) = True Then '对象初始化 If Not oExcel is Nothing Then '激活状态 bAlreadyInit = True End If End If '如果标志位是False,则创建一个实例对象 If bAlreadyInit = False then Set oExcel = CreateObject("Excel.Application") end If Set oExcelObject = oExcel End Sub ‘类销毁时退出Excel,并且释放Excel对象引用 Private Sub Class_Terminate oExcelObject.Quit Set oExcelObject = Nothing End Sub Public Sub Open(oFileName) '写入打开文件的代码 End Sub Public Function GetSheetData(oSheet) '写读取单元格的代码 End Sub '********等等,可以写更多方法******* End Class Set oExcelInstace = New ExcelWrapper oExcelInstace.Open("……")   分析:   以上脚本主要是为之前的步骤封装了一层类,以及对其类的初始化和销毁做了相应的处理,并加上了一些自定义的方法。这样,一个完美的单例模式就完成了。当创建ExcelWrapper这个类包装的时候就只会调用单个实例,在类销毁时,会自动销毁此全局变量的实例,这样就再也不会在进程里看到N多个Excel进程实例了。   单例模式在QTP中的应用:   在QTP中可以使用环境变量来代替,把之前的全局变量更改为环境变量作为对象。调用方法还是和原来的方法一样,代码如下所示: Class ExcelWrapper Private oExcelObject Private Sub Class_Initialize Dim bAleadyInit '判断对象是否存在的标志位 On Error Resume Next bAlreadyInit = IsObject(Environment("Excel_Object")) '检查对象是否已经被初始化 If Err.Number <> 0 Then bAlreadyInit = False On Error Goto 0 If bAlreadyInit = True Then If Environment("Excel_Object") is Nothing Then bAlreadyInit = False End If If bAlreadyInit = False Then Environment("Excel_Object") = CreateObject("Excel.Application") End If Set oExcelObject = Environment("Excel_Object") End Sub Private Sub Class_Terminate oExcelObject.Quit Set oExcelObject = Nothing End Sub Public Sub Open(sFileName) '写入打开文件的代码 End Sub Public Function GetSheetData(sSheet) '写入读取单元格的代码 End Sub '********等等,可以写更多方法******* End Class   总结:   单例模式可以帮助我们很好地管理对象的生命周期,使用它能够更好地帮我们集中对象的实体,更好地控制对象。后续会讲解更加强大的工厂设计模式来使脚本变得更加强大。 5.2  GUI层面向对象的扩展设计 5.2.1  层的概念   本章节主要介绍的是QTP的一种较为先进的技术,本技术是由AdvancedQTP SOLMAR自动化测试专家组所采纳的一种面向对象的设计模式,由Meir Bar-Tal于2008年12月20日发表,作者将会详细介绍此设计模式的每个类以及方法、设计模式的使用、设计模式的优缺点。   此模式主要是通过QTP描述性编程以及装载GUI对象的Dictionary对象,通过业务驱动的方式来得到体现,最有价值的地方在于,其对象识别的先发机制,可以有效的防止QTP在运行时识别对象出现卡住的现象,当对象出现不匹配时,能使测试顺利退出,并在报告中具体定位细节。从而有效降低了测试的维护量,并节省了自动化测试的时间。   自动化测试的核心问题就是如何减少维护量,例如,我们应该使用对象库还是描述性编程?如果选择“OR”,那么可以在每个Action中使用共享对象库或者本地对象库,那如果选择“DP”,可以有什么方式来实现吗?成本效益和可维护性是我们在做自动化测试中最为关注的,在此引出一个概念——GUI层扩展。这一概念经过SOLMAR自动化专家组的分析和观察已被采纳,使用它就可以尽可能地提高代码重用性(通过使用面向对象的方法来提高效率,并分解出若干个抽象层,可维护性较高)。   前面已经提到了GUI层,那层的作用是什么呢?这里先简单解释一下,层可以使重用性发挥到最大极限,定义一个类(封装一个图形用户界面层)通过相应的接口来控制应用程序GUI界面中的测试对象,从而把这个类称之为GUI层。 5.2.2  封装测试对象类   为了能够使大家更加容易理解此设计模式,在这里重新修改了原文中“层”的方法和对象,这里就通过把百度搜索页封装成GUI层作为一个最简单的例子来讲解,代码如下所示: Class BaiduSearch Private m_htChildObjects '定义变量作为Scripting.Dictionary '***************为变量设置Get Set 方法*************** Public Property Get ChildObjects() Set ChildObjects = m_htChildObjects End Property Public Property Let ChildObjects(ByRef dic) Set m_htChildObjects = dic End Property '*************初始化GUI界面上的测试对象************** Public Function Init() ChildObjects = CreateObject("Scripting.Dictionary") With ChildObjects .Add "Browser", Browser("name:=百度一下,你就知道") .Add "Page", ChildObjects.item("Browser"). _ Page("title:=百度一下,你就知道") .Add "SearchContent", ChildObjects.item("Page"). _ WebEdit("html id:=k") .Add "Submit", ChildObjects.item("Page"). _ WebButton("value:=百度一下") End With Init = IsContextLoaded(ChildObjects) End Function '**********业务行为函数 - 百度输入搜索内容*********** Public Function SetSearchContent() ChildObjects.item("SearchContent").Set "zzxxbb112" End Function '**********业务行为函数 - 百度点击搜索*************** Public Function Submit() ChildObjects.item("Submit").Click End Function End Class   首先看程序中最外层的BaiduSearch类,它代表百度的一个GUI层。我们在类中定义了一个Scripting.Dictionary对象,并且为其设置Get/Let方法。接着是一个初始化的函数Init,这个函数主要的作用是,把页面内的所有对象全部封装在一个Scripting.Dictionary容器对象中,并通过描述性编程,及结合迭代式的对象封装,有效地提高了代码的重用性。最后还有两个业务行为函数,一个是在文本框中输入搜索内容的行为函数,另一个是点击搜索按钮的行为函数,这两个函数都是可以直接在字典对象中进行搜索关键字来定位对象的,并针对对象进行操作来达成关键字字典对象驱动。这样,一个“百度”的GUI层就已经封装好了,并且这个类还具备了两种业务行为函数的接口。   下面讲另一个重要的函数,用过QTP的应该知道,当QTP在运行时,测试对象一旦发生变化,如与对象库中的对象无法匹配时,QTP就会停止运行直到超时,然后弹出错误框。在QC里执行也是一样,只是没有出现错误框,这样的情况会导致在自动化测试中浪费很多时间。因此,在GUI层中的Init函数的最后加入了IsContextLoaded函数,此函数的作用就是,检查GUI层中所有对象是否已经存在,并且把结果进行返回,实现程序如下: Public Function IsContextLoaded(ByRef htContext) Dim allExist, ix, aItems, aKeys, strDetails, strAdditionalRemarks allExist = True aItems = htContext.Items aKeys = htContext.Keys For ix = 0 To htContext.Count-1 IsContextLoaded = aItems(ix).Exist(0) strDetails = strDetails & vbNewLine & _ "Object #" & ix+1 & ": '" & akeys(ix) & "' was" If IsContextLoaded Then strDetails = strDetails & "" strAdditionalRemarks = "" Else strDetails = strDetails & " not" strAdditionalRemarks = " Please check the object properties" allExist = False End If IsContextLoaded = IsContextLoaded And allExist Select Case IsContextLoaded Case True intStatus = micPass Case False intStatus = micWarning End Select strDetails = strDetails & " found." & strAdditionalRemarks Next Reporter.ReportEvent intStatus, "IsContextLoaded", strDetails End Function   由于QTP不提供从外部文件中读取类的方法,因此,在这里添加一个创建类的函数,接着就是把外部文件封装好,直接保存为*.vbs后就可以为QTP运行服务了: Public Function CreateLogin() Dim objLogin Set objLogin = New BaiduSearch Set CreateLogin = objLogin End Function 5.2.3  调用业务行为   在QTP中加载之前保存的VBS cript文件后,在专家视图中输入图5-2所示的脚本,就可以完成业务行为的调用。 图5-2   当脚本执行完毕后,会在结果报告中显示对象是否存在,如果在运行时出现某个对象不能识别,或者说出现属性不匹配的情况,QTP就会立刻退出,并在结果报告中显示不匹配的那个对象来方便我们进行定位,其实这也是本设计模式的一个比较明显的优势。 5.2.4  对象识别结果分析   图5-3是对象识别全部通过的结果。 图5-3   图5-4是个别对象识别未通过的结果图。 图5-4   通过图5-4可以看到,当对象出现不匹配时,我们能很容易地在结果报告中进行定位。   总结:   优点:   ● 高效的重用化,有效减少了代码的维护量。   ● 关键字字典驱动有效地提高了编码的效率。   ● GUI层提供的行为函数接口,使脚本与业务能够更好地关联起来。   ● 对象检查的先发机制可以有效防止QTP“卡住”的现象。   ● 对不匹配的对象能够在结果报告中自动定位。   缺点:   ● 由于QTP缺少“类”这一功能的提示,因此,脚本编写没有了代码提示功能。   ● 前期工作量较多,比较适合大型的自动化测试项目。   ● 需要有一定的编程能力。 5.2.5  总结   此设计模式是一种自动化测试设计思想,本章主要讲的是脚本的设计模式,没有牵涉对象库。如果单独使用对象库会增加代码的开发量和维护量,必须把DP描述提炼成基于外部的对象库来单独管理测试对象,推荐与框架一起结合使用方法。 6.1  框架设计理念   阶段要点   ● 核心框架简介。   ● 框架设计图解。   ● 框架结构透析。   ● 框架目录细分。 6.1.1  框架核心介绍 6.1.1.1  自动化测试框架简介   软件自动化测试框架一直以来都是自动化测试的终极目标,但是,很多测试新手会误解其真正的含义,要么就是把它看得相当深奥、复杂,要么就是随便构建了些脚本就自认为是一个框架。在国内的论坛上“测试框架”一直是一个非常敏感的词汇,讨论的激烈程度也是远远高于其他一些测试技术问题。目前国内一直没有一个比较成熟的自动化测试框架,导致国内很多测试人员对框架的理解出现参差不齐的现象。   那么什么才是自动化测试框架呢?自动化测试框架究竟能够为我们带来什么呢?其实自动化测试框架就是一种规范的集合体,在自动化测试团队开发过程中,经常会遇到很多这样和那样的问题,例如:   ● 测试脚本不统一,出现很多重复脚本;   ● 在对象库中含有很多重复对象,导致对象混乱;   ● 测试脚本全部为Hard Code,没有实现部分可配置,导致维护成本过大。   以上这些只是一些比较常见的问题,其实在自动化测试过程当中遇到的问题还远远不止这些,那么要解决这些问题,就必须要为其定义适合项目的规范。例如,脚本不统一,可以对每个脚本写法定义严格的规范,定制共享函数库;对象库重复混乱,可以使用共享对象库来解决;测试脚本全部为Hard Code,则可以把部分的关键字进行分离。这些都是可以采取的措施,当然还包括脚本实行可配置、测试执行管理、状态监控、报表管理等,当这些措施规范都集合在一块的时候,我们就把其称之为自动化测试框架。 6.1.1.2  用例解析驱动测试介绍   在本章将展示自己的原创自动化测试框架,但在展示测试框架之前,先介绍框架的整体思想:个人认为,一个好的测试框架应该能够让更多人利用起来,而不是只有开发框架的本人才会使用,这才是框架所需要做的。前面已经提到了,框架是一个标准规范的集合体,因此,一旦制定好了一定的规范,别人就能够根据此标准规范来进行测试脚本的编写。那么,如何才能最大限度地体现出这一点呢,作者根据国外的一些经验总结出了一套属于自己的“用例解析驱动测试”理论。什么是用例解析驱动呢?本书的一开始就已经介绍过了自动化测试并不是简单的写写脚本,在写脚本之前需要考虑并验证很多环节才可进行到编码阶段,一般会有以下这样几个阶段:   ● 需求分析→自动化测试需求分析→自动化测试方案制定;   ● 自动化测试设计→自动化测试用例设计→自动化测试脚本设计与开发;   ● 执行自动化测试用例→提交测试结果→测试结果分析。   这是一个完整的自动化测试流程,但在实际项目中往往没有这么简单,就如同下面这样:   自动化用例设计→ 重新设计脚本 →重新维护测试脚本   当应用软件完成了一个版本的测试之后进入到下一版本时,此时会需要重新设计测试用例,有些需要添加,有些则需要对用例进行更新,在更新完毕之后,还是需要重新再把更新的用例转化为适合最新版本的测试脚本,然后再继续执行提交结果。相信在这样一个流程中你会发现,只要每次一有新版本出现,如果应用软件先前的设计一旦有变动,那么就需要重新编写测试用例,并将用例转化为自动化测试脚本。作者认为这样的流程是存在问题的,其中存在着许多重复的无用功,完全可以把测试脚本包装一层,抽象到测试用例层面,让测试用例直接驱动自动化测试脚本,而不是每一次更改都需要重新转化测试脚本,如下:   自动化用例设计→ 自动转化脚本 →自动生成测试脚本   利用这种用例解析驱动测试模式可以减轻每次人工去重写维护脚本的时间。那么,如何完成这个中间过程呢?我们知道每个人写出来的用例都是不一样的,要做到让所有人写出来的用例都能成功转化成脚本,那是根本不可能的,因此,这里首先要做的就是,要把用例的标准规范进行统一化,把用例的每个步骤进行标准化的拆分:   ● 测试对象(具体的对象关键字);   ● 测试行为(具体的行为函数名);   ● 测试数据(具体的测试数据)。   一旦规范化好了这些元素转化,工作就会容易很多,可以严格要求测试用例的格式只能使用这三种元素的关键字的组合,去代替平常的一些测试用例,这样的做法还有一个好处就是,统一测试用例、规范测试用例,让清楚规则的人一看就能够理解测试用例。这样就不会造成看不懂别人所写的用例的尴尬局面,并且能够带动测试脚本的统一化。那么如何才能把标准化的用例具体化到脚本层呢?很简单,如图6-1所示。 图6-1   从图6-1中可以看到,在自动化测试脚本中,最关键的元素就是测试对象、测试行为和测试数据,测试对象由父对象和子对象组成;测试行为在脚本中即为一个动作事件,如点击,输入、验证等;测试数据即为需要输入的数据。 图6-2   如图6-2所示就是正确的标准化用例写法。此种写法有很多好处:第一,对于用例描述一目了然,比文字性描述理解起来更加容易;第二,这样的写法对于最终生成自动化测试脚本较为容易实现。最终生成脚本的样式: SwfWindow("首页").SwfEdit("用户名").Set "Mercury" SwfWindow("首页").SwfEdit("密码").Set "Mercury" SwfWindow("首页").SwfEdit("登录").Click   所有的脚本都将会是动态生成的,可以看到,脚本中的关键元素在用例中已经全部纳入。那么由这种方式从用例层解析,并自动地具体化到脚本层的模式称为“用例解析驱动测试”。 6.1.2  框架设计总图   框架设计总图如图6-3所示。 图6-3   说明。   Test Object Pools:   测试对象池,用于存放所有的测试对象,并供用例层进行读取。   Test Case Generator:   自动化测试用例生成器,方便测试人员根据一定的标准来完成用例。   Error Handle Control:   错误处理控制器,对错误实时监控,发生错误会进行相应的控制。   Test Execution:   测试执行调度,利用AOM自动启动QTP执行脚本,并进行过程控制。   Report Engine:   整个测试运行完毕,生成完整的测试报告。 6.1.3  框架结构细分 6.1.3.1  TestObject Pools   对象库一直是自动化测试脚本中的精髓,它可以把自动化测试脚本中的对象属性描述与脚本完美的分离,以便有利于脚本的维护,当然这也是框架中必不可少的。团队开发中往往会采用公共对象库,并且放置于一个所有人都可访问的位置,本框架采用了与QC相连并进行远程调用,最终把TSR文件转化生成XML文件,此处的XML文件主要是用于用例解析的读取,具体流程如图6-4所示。 图6-4   流程:   ● 利用QC的开放接口OTA连接Quality Center;   ● 调用MFL模型“Mercury.FileLocator”获取到最新TSR共享对象库;   ● 自动更新到本地对象库;   ● 利用ORU模型“Mercury.ObjectRepositoryUtil”把TSR转化为XML。   注意:此框架的主要对象库的存放空间就是对象池,在框架中的对象池中存放着TSR文件和XML文件,主要实现了两种方式的更新。   (1)自动更新来自于QC的共享对象库。   (2)手工更新本地对象库(没有QC的情况下)。 6.1.3.2  TestCase Generator   用例生成模块可以说是整个框架的核心部分,它可以方便测试人员选取列表中的测试对象、测试行为,并可输入自己想要的测试数据来生成用例。从连接对象库到获取到对象库中的关键字,并组成测试用例步骤,最后完成一个完整的测试用例这样一个过程中,始终贯彻用例解析驱动测试模型来进行,此模块在后面也会详细讲解,如图6-5所示。 图6-5   流程:   ● 连接测试对象池;   ● 利用XML Dom读取对象池中的XML对象库文件;   ● 映射读取到的测试对象到用例生成器中;   ● 完成每一个测试步骤生成测试用例;   ● 合并到测试用例库中。   此模块中的XML文件其实就是之前测试对象池中的从tsr文件自动转化的结果文件。然后采用单选框的方式读取到测试对象库中的所有父对象和子对象,用以避免测试人员手工输入时出现错误而导致脚本最终执行出现没有必要的错误。 6.1.3.3  Test Execution   Test Execution称为测试执行模块,可以完成多脚本的运行,运行不同状态的用例。负责实现全局测试流程控制,主要技术实现方式还是依靠QTP的自动化测试模型AOM,包括初始化QTP、自动加载初期设置、自动生成测试脚本、运行脚本、生成结果报告,如图6-6所示。 图6-6   流程:   ● 选择需要执行的测试用例。   ● 初始化QTP。   a.动态加载相对路径(RP)。   b.动态加载场景恢复(RS)。  c.动态加载公共对象库(OR)。   d.动态加载公共函数库(FL)。   ● 脚本动态生成。   a.读取对象关键字元素。   b.拼接测试脚本。   c.载入生成脚本到QTP。   d.使用RegisterFunc注入自定义测试行为。   ● 运行测试与结果报告。   a.执行测试用例。   b.载入Results.xml和PDetails.xml。   c.错误监控实时发送结果报告。   d.执行完毕,生成测试结果。 6.1.3.4  Error Handle Control   错误处理在自动化测试过程中一直是一件非常繁锁的事情,我们经常会遇到因为脚本的一个小错误而“卡住”所有其他测试用例的执行,也经常在复杂的框架中无法对自己脚本的执行结果出现的错误进行定位,这些问题我们都可以通过QTP的Recovery Scenario这个场景恢复机制来解决,如图6-7所示。 图6-7   流程:   ● 执行自动化测试用例。   ● 监控执行状态,一旦发生错误即调用错误处理模块。   ● 启动自定义场景恢复RecoveryFunction定位错误。   ● 把错误定位信息注入测试报告。   注意:此处在场景恢复中并没有加入异常弹出窗口的控制,因为每个项目都会有不同的情况,这部分内容可由读者自行抓取异常窗口后,加载到场景恢复的函数中即可。此处只用作定位错误测试对象以及错误测试行为,方便测试人员进行随后的调试。 6.1.3.5  Report Engine   一个好的测试报表能够让最终执行的测试结果一目了然,它包括执行时间、执行用例名称、执行结果状态、详细结果报告等。由于QTP本身不带有多脚本运行的测试结果状态,需要借助其他辅助工具才能实现,并且每个用例脚本的测试结果的HTML输出也是需要手工才可进行输出的,因此,在此框架中加入了动态生成HTML的方式,如图6-8所示。 图6-8   流程:   ● 测试脚本运行完毕自动调用Report引擎;   ● 载入模板文件PDetails并生成Log日志;   ● 外部载入Result.xml并结合模板文件转化为结果文本;   ● 转化为HTML写入用例结果,以及全局测试结果状态报表。   此处的Result.xml结果文件需要在QTP执行完毕后,通过Excel宏进行获取,因为,如果在QTP中执行会存在一个问题,QTP本身的设定是,当QTP还没有执行完毕时,本身是不会生成任何结果文件的。就算把脚本内的Result.xml复制出来也是空的,因此,唯一的解决方法就是,等待QTP运行完毕,利用外部脚本对其结果文件进行解析。 6.1.4  框架目录结构   框架的核心采用的开发平台为Excel,主要还是考虑到Excel使用起来非常方便、高效,利用Excel强大的Macros来完成框架的模型,具体的框架目录结构如图6-9所示。 图6-9   如图6-9所示,框架主要包含了以上几个模块文件夹。   ● frameworkIco:此文件夹主用于存放一些框架所需要用到的图片。   ● ObjectRepository:此文件夹用于存放对象库文件,包括tsr和xml格式的。   ● QtpTest:存放所有的QTP test的文件夹。   ● recovery Sceniao:此文件夹用于存放场景恢复主文件以及自定义场景恢复函数库。   ● TestCaseGenerator:自动化用例步骤生成器,此文件夹内存放着框架核心Excel文件。   ● TestExtensibilityConfig:此文件夹存放着框架的一些函数扩展接口,对象的默认方法。   ● TestLibrary:此文件夹用于存放一些公共函数库。   ● TestLog:此文件夹用于存放测试结果报告。   ● TestScriptHistory:此文件夹用于存放历史脚本记录。

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档