本节书摘来自异步社区《精通qtp——自动化测试技术领航》一书中的第1章1.8节数据池(data table)的应用,作者余杰 , 赵旭斌,更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.8 数据池(data table)的应用
精通qtp——自动化测试技术领航
阶段要点
熟悉测试数据和脚本业务分离的好处和优势。
学会利用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,使用它配合代码来管理好繁杂的测试数据才是自动化测试的正道。接下来,让我们进入第一个实例,先来看下面这段代码:
然后,图1-195是脚本对应的对象库,读者可以看一下。
最后,我们解读一下脚本:这段脚本其实还是比较简单的。先打开一个浏览器,然后进入百度的首页,接着输入关键字“qtp自动化测试技术领航”,最后点击“百度一下”按钮。这样,百度会进入搜索匹配页面,此时便可以开始做很多测试验证。当然,这里验证省略掉了,因为这是后面章节的内容。那我们假设验证是通过的,成功匹配到了“qtp自动化测试技术领航”的一些相关信息,那么通过点击“到百度首页”这个image对象回到首页,因为需要再一次输入测试数据,再进行n次测试,测试什么?就是测试关键字输入后点击搜索能否正确搜索到相关的信息。所以,在这里写了一个循环。这样,qtp就能测试10次了,这个结果应该很可靠了吧?如果10次都通过了,那这个功能就应该没问题了。
单从表面上看,的确是!但是,同样面临一个问题,那就是在用同一个测试数据“qtp自动化测试技术领航”在做测试。所以,执行1次和执行上百次是没有什么实质性的区别的,特别像这种搜索框之类的功能点,这样的自动化测试也算是一种浪费时间的无效测试(特殊情况除外,如可靠性测试)。怎么才算有效?从手工测试的角度上来说,就是输入完全不同的测试数据,然后查看测试结果。那qtp该怎么做呢?很简单,只要把关键字输入框中set的数据改掉就可以了,如下面这个脚本:
上面这段代码最后完全实现了想要的效果,但是,缺点大家都已经看到了,有太多的重复内容,假设是要输入100个测试数据呢?复制、粘贴100次吗?显然不需这样,完全可以将set的值进行参数化。先给出第一个解决方案,使用前面第一段代码相同的循环,但又每次输入不同的关键字,代码如下所示:
我们来分析一下这个脚本,其实这段代码几乎和先前的那段代码没有两样,作者只改动了一句而已(代码里用注释标注的句1)。解决方案其实也不难,就是把每次循环的计数和测试输入组合,这样每个测试数据就都不一样了。不过,读者应该发现了吧,在这里并没有直接将测试数据明确写出来,取而代之的是datatable.value("关键字输入","action1")这句话。这个就是本章节介绍的参数化,通过datatable参数化。
作者把测试数据封装到了datatable.value("关键字输入","action1")里,如图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所示,可以看到当前一共有两个sheet,它们分别是global和action1。接下来,暂且不对global“做文章”,只对action1做文章。那么,操作步骤正式开始。
step 1,选中“action1”这个sheet,将鼠标光标移动至column“a”,然后双击,如图1-198所示。
step 2,待弹出change parameter name窗口后,任意输入一个name值,这个值将会变成a列的列名。在这里,作者输入了“演示”这两个字,最后点击ok,如图1-199(注:本步骤可以省略,这样的话,列名等于a)。
step 3,到此为止,已经完成了一大半的操作,已经为测试数据建好了一个“家”,以后它们完全可以搬到这个位置“居住”。接下来,在对应“演示”这列的第一行交叉处写入一个值,这个值就是测试数据,如图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.8.2.3 global sheet vs local sheet
如果有这样一个测试需求—进行3次百度搜索的业务流程,但是每次输入的关键字必须不一样,此时qtp该怎么完成?请看参考答案,见如下脚本:
'打开网页 -- 第1次
systemutil.run "www.baidu.com"
'输入关键字“test1”并点击搜索
webedit("关键字输入框").set "test1"
webbutton("百度一下").click
'关闭网页
browser("百度一下,你就知道").close
'相同代码 -- 第2次
'只变更测试数据
webedit("关键字输入框").set "test2"
'相同代码 --第3次
webedit("关键字输入框").set "test3"
正确答案已经公布了,运行成功,自动化测试脚本成功实现!但是,这就是我们想要的吗?但是如果要输入10次不同的测试数据呢?甚至于上百次呢?难道也要跟着复制→粘贴100次吗?那这脚本该有多庞大啊!
有什么更好的解决方法吗?答案是肯定的,那就是接下来要介绍的global sheet。在上一个小节中,所讲的实例是用local sheet(action1)来完成的,那么接下来就来看下面这段代码,看看用global sheet是否能够圆满完成任务,脚本如下所示:
'打开百度首页
'将datatable里的值传递给一个变量
testdata = datatable.value("关键字输入","global")
'使用该变量,并将其填入关键字输入框
webedit("关键字输入框").set testdata
该脚本仅用了几行代码就能完成3次业务循环吗?而且还要用不同的数据?别着急,我们来运行一下该脚本,看一下运行结果,是否自动化测试圆满完成了,如图1-203所示。
图1-203是qtp的另一个重要模块“测试报告(test results)”,这个在后面的章节会详细介绍,这里读者只需要看运行结果就行了。我们可以看到,qtp运行了3次迭代,这就证明了qtp的确进行了3次业务循环。那接下来,再来证明这3次业务循环所用的测试数据都是完全不同的,如图1-204所示。
如图1-204所示,点击run-time data table以后,可以看到脚本在运行过程中所使用的所有数据记录,分别是test1、test2、test3。到此,大家应该没有任何疑问了吧?的确是运行了3次业务,切实地输入了各不相同的测试数据。想知道这些测试数据哪里来的吗?答案现在正式揭晓,如图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所示,这就是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所示。
然后,仍然复用之前的脚本,唯一更改的一处就是把引用globl sheet改成引用local sheet,代码如下所示:
'唯一的区别就是把global改成了action1
testdata = datatable.value("关键字输入","action1")
browser("百度一下,你就知道").page("百度一下,你就知道").webedit("关键字输入框").set testdata
browser("百度一下,你就知道").page("百度一下,你就知道").webbutton("百度一下").click
然后,运行脚本,来看一下尽管local sheet中有3行测试数据,但是是否只运行了一次,如图1-209所示。
很明显,图1-209已经给出了结果!通过这两个实例,已经明确了它们之间各自的用处。但是呢,data table的确是qtp的一个难点,即使global sheet没有数据,仍然可以通过别的方式(代码控制的方式排除在外)去执行local sheet下的所有行。在test flow(后续章节会介绍)里反击action后可以进入action的一些相关设置,如图1-210和图1-211所示。
如图1-211,界面很熟悉吧?它和global的data table iterations设置界面相似,在这里选中run on all rows就可以执行local sheet下的所有测试数据了。虽然都是执行所有行数据,不过它们的区别还是很大的,先看一下执行结果,如图1-212(脚本略)。
光看一张图可不行,绝对不够直观,再回过头看之前global的那张图,如图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就执行不到最后一行了。
1.8.3 test datatable vs run-time datatable
这个标题很熟悉吧,在本书的前面几个章节,介绍过test object和run-time object。在这里,我们又和test…以及run-time…见面了。回顾一下,test object是什么意思?就是固定在测试对象库里的测试对象。那么run-time object呢?就是程序运行时实际的测试对象。那么在这里test datatable和run-time datatable的理念完全是借鉴它们的。先来看一下test datatable和run-time datatable之间的区别和含义。
test datatable—在data table里事先准备好的、固定的测试数据,它是一组静态数据,是由自动化测试工程师人为事先填写进去的。
run-time datatable—在qtp执行过程中,将测试数据填写到data table里,qtp运行结束,测试数据就消失(不会保存在data table里),但是可以在测试报告中看到它。
先来看一下test datatable,这个就相当简单了,并且之前就已经全面地做过实例(global sheet和local sheet),可以手工在data table中设置一些测试数据。主要来看看run-time datatable究竟是怎么回事。
我们试想一下,现在有一个注册的业务需要进行自动化测试。因为自动化测试有模块化和细分化的原则,所以,需要把这个业务拆成两个脚本,第一个脚本主要做注册的操作。而大家都知道通常现在的任何网站都会在注册后进入另一个验证界面,往往要验证一些基本信息,比如,确认你填写的用户名、生日、住址等是否正确。所以,把这个验证的业务过程封装到第二个脚本中去。那么问题就来了,如果一个测试业务由两个脚本组成,那么它们之间的数据中转该如何进行?即将第一个脚本中填写的注册信息放到第二个脚本中去验证。此时,通常情况下有两个比较好的方法,第一个就是通过eom转出数据,这个是后续章节的内容,这里不多做阐述。而另一个好方法就是利用qtp自带的data table,方法与实现步骤如下(假设以注册举例,只在注册后的验证界面中校验用户名这个数据)。
(1)填写完所有必填项,并使用getroproperty来获取已填写的用户名。
(2)将这个动态的获取到的值动态地传入global sheet的某个指定列中。
(3)在第二个脚本(校验注册信息的这个页面)中动态读取这个值并做判断校验。
(4)脚本运行完毕,run-time数据消失,不过可以通过test report来查看脚本在运行时使用过的所有测试数据。
那么接下来就来做一个实例,需完成如下业务。
(1)进入百度首页。
(2)在搜索框中输入“qtp自动化测试技术领航”字样。
(3)点击“百度一下”按钮。
(4)校验在搜索结果页面中的搜索框中是否保留了刚才输入的字样。
在看到这个业务后,先进行业务分析,并大致给出一个实现思路,步骤如下所示。
脚本1。
(3)使用getroproperty去获取搜索框中刚才输入的字样,并传入global sheet的指定列中。
在这里读者肯定要问,为什么不直接把“qtp自动化测试技术领航”字样传入global sheet,而要多一个步骤通过getroproperty去获取?因为,如果你直接传入数据,就少了一个验证是否成功输入到搜索框中的过程了,这样的自动化测试是不可靠的。
脚本2。
(4)读取global sheet中的run-time data。
(5)获取搜索结果页面中的搜索框的值,假设为checkvalue。
(6)将run-time data与checkvalue做比较。
最后,通过上述分解步骤完成脚本如下:
脚本1:
webedit("关键字输入框").set "qtp自动化测试技术领航"
run_time = browser("百度一下,你就知道").page("百度一下,你就知道")._
webedit("关键字输入框").getroproperty("value")
'将run_time传入global sheet指定列
datatable.value("runtime_data","global") = run_time
图1-214就是设置global sheet中的列。
由图1-214可以看到,这个单元格里是没有数据的。接下来继续看看脚本2。
脚本2:
run_time = datatable.value("runtime_data","global")
checkvalue = browser("百度一下,你就知道").page("百度搜索结果页面")._
webedit("结果页面_关键字输入框").getroproperty("value")
if checkvalue = run_time then
else
end if
最后就是将脚本1和2组合在一起的脚本x了,这里不贴出这部分脚本,因为这将是下一个章节的学习内容。
从脚本2中可以看到,如果最终验证通过了,会弹出对话框,显示“passed”。那么,来看一下运行结果到底是什么呢?最终是否验证成功呢?如图1-215所示。
我们可以看到,自动化测试圆满成功!接下来,让我们见证是否run-time data的生命周期相当的短,qtp运行完毕就会消失,如图1-216所示。
再让我们看看,qtp运行期间是否有数据被填写到runtime_data这个列中,如图1-217所示。
最后,让我们验证是否可以在qtp的test report中看到运行时所用到的数据,如图1-218所示。
验证全部结束,一切都和叙述得完全一致!到此,相信读者都已经对qtp的datatable有了更深刻的认知。
细心的读者一定注意到了,好像这个小节中都在使用global sheet。所以,相信读者一定会有疑问“为什么不能用localsheet?”因为针对将不同脚本组合成一个业务脚本的这种形式,用local sheet是不能将值传递给另一个脚本的,只有global sheet才是被共享的!就算不是用组合脚本这种形式,把所有的业务写在一个脚本中,用action划分,那为了共享不同action间的数据,仍然要使用global sheet。
至于如何把两个脚本嵌入到一个脚本中,这是后续章节的内容。
1.8.4 用好datatable对象使脚本更加灵活
因为qtp的datatable是一个保留对象,所以,mercury在实现此功能的时候为它写了很多的实用方法。使用好这些实用的方法,可以让我们的脚本更加灵活、多变。作者接下来列举一些常用的方法供读者参考:
1.动态地在datatable中添加新列并赋值
datatable.globalsheet.addparameter "column1","value1"
datatable.globalsheet.addparameter "column2","value2"
datatable.localsheet.addparameter "column3","value3"
运行结果图1-219和图1-220所示。
global sheet local sheet
2.动态地在datatable里增加新行并赋值
datatable.getsheet("action1").setcurrentrow ( 2 ) '句1
datatable.value("column4","action1") = "row2"
'或者也可以是:
datatable.value(1,2) = "row2"
运行结果图1-221所示。
必须使用“句1”才能新增行,不然会被原值替代。因为qtp在getcurrentrow这个过程结束后仍然处于第1行。
运行结果如图1-222所示。
如图1-222所示,我们可以看到,“row2”这个值被赋到了第一行中。
3.动态获取datatable中指定列的值
以上两种其实我们已经在之前的实例中都用过了,还有一种更加简单的写法,在上面也已经用过,就是直接输入列的序号,比如引用第一列就直接输入1,依此类推,完全不用去关心“列”具体叫什么名字,代码如下所示:
getvalue3 = datatable(1, "action1")
msgbox getvalue3
也正是因为这样,完全可以引申出另一个应用:循环读取action1的n列,代码如下所示:
for i = 1 to n
msgbox datatable(i, "action1")
next
如果有两个action,分别是action1和action2,要想使它们在获取的时候保持读取的行数一致,可以使用下面两种方法:
'方法1:直接定位action2的行。
datatable.getsheet("action2").setcurrentrow(2)
'方法2:使用变量传递保持行数一致
currentrow = datatable.getsheet("action1").getcurrentrow
datatable.getsheet("action2").setcurrentrow(currentrow)
4.动态获取datatable中指定行的值
getvaluebyrow = datatable.getsheet("action1").getparameter("column7").valuebyrow(2)
msgbox getvaluebyrow
5.动态获取datatable中当前行和设置当前行
'1、获取当前行
'msgbox currentrow
'2、设置当前行
datatable.getsheet("action2").setcurrentrow(5)
'注意:增加action2的列数跟行数的计算没有任何关系
datatable.getsheet("action2").addparameter "column8","value4"
currentrow2 = datatable.getsheet("action2").getcurrentrow
'msgbox currentrow2
6.获取得到datatable总行数的命令
allrowcount = datatable.getsheet("action1").getrowcount
msgbox allrowcount
1.8.5 总结
datatable是qtp的一个亮点环节,但是也是qtp的一个难点。比如global sheet和local sheet的区别以及它们的逻辑应用。还比如在1.8.4小节中介绍的一些关于datatable的应用,只是列举了一些个人认为比较常用的,其实还有很多方法经常会被使用到。不过读者完全不必担心,很多东西在qtp强大的“f1”里都有参考资料和示例,包括举例的那些方法在f1里一个都没有漏掉!不光要用好帮助文档,最终读者要做到的是,能够看着示例举一反三,随意地应用到项目中去。下面就是关于datatable的示例截图,如图1-223所示。
比如点击图1-223中的getcurrentrow method链接就能够进入到该方法的具体示例,如图1-224所示。
知识点巩固和举一反三练习
一、查阅“兵书”,根据下述要求写出相应的代码。
(1)删除qtp的action1这个sheet。
(2)将某个excel表格全部导入qtp的datatable中。
二、统计qtp当前datatable的sheet总数,并将该数据填入global sheet的“1行1列”。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。