本节书摘来自异步社区《精通QTP——自动化测试技术领航》一书中的第1章1.7节描述性编程(Descriptive Programming),作者余杰 , 赵旭斌,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.7 描述性编程(Descriptive Programming)
精通QTP——自动化测试技术领航
阶段要点
描述性编程不高深。
描述性编程的两种写法。
描述性编程实例介绍。
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了,它是一个对象库,里面添加了一些需要操作的百度网站的对象。接下来我们来看代码的实现:
Browser("百度一下,你就知道").Page("百度一下,你就知道")._
WebEdit("wd").Set "QTP自动化测试技术领航"
Browser("百度一下,你就知道").Page("百度一下,你就知道")._
WebButton("百度一下").Click
以上这段代码读者应该也已经很熟悉了。它就是用对象库编程产生的代码。但是,读者有没有思考过一个问题,假设这些对象没有添加到对象库里怎么办?QTP还能工作吗?完全可以!QTP提供了描述性编程(Descriptive Programming)这个解决方案。事实上,在很多情况下,对象都不会顺我们的意思,经常会面临“不是我们想不想把对象添加到对象库,而是根本没法添加”这种尴尬局面。所以,此时“DP”可以担起重任了,替“ORP”完成它无法完成的事!这里暂时先不介绍无法添加对象的案例,我们先存心使对象无法生存于对象库中。把之前添加好的百度的对象全部删除,如图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)换成一种描述语言,它描述的仍然是这个对象,只不过不再是封装好的现成的对象,而是需要现场描述(封装)。
第一种描述形式已经介绍完了,但是关于第一种描述性编程方式的内容还没有讲完,在前面那段代码中,都是以单属性及其属性值来描述一个对象。其实在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报错的截图。
但是子对象如果描述了,父对象可以不描述,当然,父对象不描述又不报错的前提是要被添加到对象库中,一起来看下面这个示例脚本:
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。通过OI就可以知悉一切对象可描述属性,如图1-185所示。
如图1-185所示,这就是OI的界面,应该很熟悉了吧,之前都已经认识了,所以在这里一些基本介绍就不重复了。现在选中的就是刚才描述过的一个控件“WebButton”,默认出现在Mandatory Properties窗口里的就是系统默认的一些该控件的重要属性,如果要描述一个控件,首先先描述这些最重要的属性(也许这种说法不科学,但是本人觉得这说法很实际和现实)。如果默认的属性满足不了你的需要,那你只需点击Add/Remove就可以了,里面可以让你新增或删除一些其他的属性,如图1-186所示。
当然,不但可以删除一些备选的属性,也可删除系统默认属性。另外,在此主要要讲一下Browser和Page对象的描述,先来看看OI吧,如图1-187和图1-188所示。
我们可以看到,Browser和Page是没有系统默认属性的。因为这两个控件比较特殊,一般情况下,都会使用Class Name或者creationtime这两个属性来描述该对象,它们在OI里可是没有的。Class Name可以从Spy里看到,说到Class Name我们也不得不回顾一下之前讲过的一个挺重要的知识点,就是Class Name必须要写成micClass。读者再返回看看在上一个小节中,描述Browser和Page的时候是不是使用了micClass?
接下来讲解的是Spy,Spy在描述性编程中也是经常被调用的,因为OI用于定位所要描述对象的属性,而Spy就是定位描述属性的属性值,它们绝对是默契的搭档!描述性编程也就是因为有了它们,才可以应用起来,如来看看下面这张Spy图,如图1-189所示。
本小节的内容相对比较简单,不过简单不代表实用性就不高,恰好是相反的!最后,告诉读者描述性编程可描述的属性都是封装接口属性,不是自身接口的属性,务必切记!
1.7.4 描述性编程的妙用以及与对象库编程的混搭
本人是不支持使用纯描述性编程的,DP的使用应审时度势且以实用的角度出发。前面也讲到过了,一般情况下,描述性编程应用在对象库编程无法完全满足需求的时候,以替补的身份去出色完成少额的任务。但是要遵循一个原则,那就是不到必不得已(对象库编程实在没法解决),绝对不要去使用描述性编程,千万要杜绝想使用了就随意使用的情况。这样做有什么好处?最大的好处就是可以使脚本一目了然从而增加了可维护性!当一段脚本中都是对象库编程,突然有一行出现了描述性编程,那即使过了几个月后再看脚本,我们也可以马上明白,当前步骤是比较特殊的,是对象库编程完成不了的。反之,如果不养成这个好习惯的话,那整个脚本的层次就会显得非常乱,时间久了一定分不清哪些使用描述性编程的语句属于没必要范畴,而哪些属于必要范畴。
接下来,就举一些项目中需要使用到描述性编程的案例。
(1)百度首页有很多相同的Link控件,如“新闻”、“网页”、“图片”等,全部添加到对象库很麻烦,那么该如何使用描述性编程完成?
首先,先来看下场景图,如图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所示。
分析。
这段脚本首先用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所示。
现在要对这些输入框做文章,在每个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所示。
然后来看一下设定单个乘客时的脚本如下所示:
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所示。
当系统对于发生多个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方法描述素材中的代码。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。