Ⅰ.11 如何实现Keyword-Driven Testing

来源:互联网 发布:ubuntu find命令 编辑:程序博客网 时间:2024/05/16 18:42

Keyword-driven testing测试(也称作'table-driven testing' 和 'action-word testing')是一种测试方法论,使用这种方法测试完全由数据驱动。使keyword-driven testing不同于data-driven testing的是:用后者我们读进数据项,例如,为了定位一个GUI table,但是用前者时数据项不仅仅是数据也可以使特定AUT函数的名字,以及当测试运行时它们执行的参数。

根据高级AUT行为如'Add Item' 或者 'Delete Item',keyword-driven testing 最大的优点是可以纯粹的作为数据tables创建一个测试,上述AUT行为是测试者熟悉的并且AUT可以关联到的,期间不需要知道更多的引擎下的技术。

keyword-driven 测试包括两个方面。首先是一次性的创建一些特定的AUT测试脚本函数来翻译数据,一次性的创建一个通用'driver' 函数,该函数从数据资源中读取数据,执行AUT-specific基于数据的测试脚本函数。第二个是创建一个或者多个测试案例以及相应的用于驱动测试的数据tables。

在这个部分我们将首先看一下一个测试员在创建一个测试案例和相应的测试数据时是如何做的,以及结果是如何产生的,然后我们会看一下场景背后一次性必须完成的工作,这样可以使它全部工作。

这个部分展示的所有的例子都在Squish的示例目录中(Python版:SQUISHDIR/examples/qt/addressbook/suite_keyword_py; JavaScript 版:SQUISHDIR/examples/qt/addressbook/suite_keyword_js,以此类推)。测试案例叫做 tst_keyword_driven。

尽管我们已经使用了一个基于Qt的AUT,基础的GUI工具包并不影响——使用该部分给出的思想你可以为任何Squish支持的工具包创建 keyword-driven testing。

Ⅰ.11.1 如何创建一个 keyword-driven 测试

keyword-driven需要的测试案例通常是相同的——并且极为简单:

source(findFile("scripts", "driver.py"))def main():    drive("keywords.tsv")
首先在通用的“驱动程序”功能中加载测试程序,然后它在一个测试数据文件中执行 drive() 函数。测试数据文件精确的指定了什么样的行为应该发生,以开启AUT开始,以终止AUT结束。

下面是一个典型的类似这样的数据文件:

->KeywordArgument ->1Argument ->2Argument ->3Argument 4->startApplicationaddressbook->->chooseMenuItemFileNew>->verifyRowCount0->->->addAddressRedHerringred.herring@froglogic.->com555 123 4567->->->addAddressBlueCodblue.cod@froglogic.->com555 098 7654->->->addAddressGreenPikegreen.pike@froglogic.->com555 675 8493!verifyRowCount3->removeAddressgreen.pike@froglogic.com->removeAddressblue.cod@froglogic.com->removeAddressred.herring@froglogic.com->verifyRowCount0terminate
(我们已经用符号->标出的以tab作为间隔。)第一行包含了字段名,其他的行包含了待执行的行为。在每个行为行中第一列包含了待执行的顶层特定AUT函数的名字,剩下的列包含了函数可能需要的任何参数。

这里,terminate 函数不需要参数,verifyRowCount 和 removeAddress 函数两个都需要一个参数(前者是一个待验证的行技术器,后者是一个标识待删除行的email地址)。
相似地, chooseMenuItem 函数总是需要两个参数(菜单名字和菜单中菜单项的名字),addAddress 函数需要四个参数(forename,surname,email,phone)。

这里是由一个典型的测试运行的结果(连带数据和大多数的假数据):

Start tst_keyword_drivenLog Drive: ’keywords.tsv’Log Execute: startApplication(’addressbook’)Log Execute: chooseMenuItem(’File’, ’New’)Log Execute: verifyRowCount(’0’)Pass VerifiedLog Execute: addAddress(’Red’, ’Herring’, ’red.herring@froglogic.com’,’555 123 4567’)Pass VerifiedPass ComparisonPass ComparisonPass ComparisonPass Comparison...Log Execute: verifyRowCount(’3’)Pass VerifiedLog Execute: removeAddress(’green.pike@froglogic.com’)Log Removed green.pike@froglogic.comPass Verified...Log Execute: verifyRowCount(’0’)Pass VerifiedLog Execute: terminate()
很显然我们可以很容易的想添加多少就可以添加多少keyword actions——相当独立于测试案例脚本,该脚本将简单的执行任何数据文件中测试指定的东西。当然,如果需要附加的行为——例如一个 editAddress 函数——不知为何不能将它作为一个特定AUT函数添加进去,一旦添加进去然后它可能一同所有的测试用在数据文件中。

因此,从一个测试者的角度来看,测试者想做keyword-driven testing,工作是明确的。然后,对于这个待实现的简单性一同通用驱动函数都需要写出测试数据需要执行的特定AUT函数。这些内容将在接下来的两个部分讲解。

Ⅰ.11.2 如何创建 支持keyword driven的AUT-specific测试

测试数据中每个指定的keyword行为必须有一个相应的接受给定参数以及指定给定行为的函数。对此没有通用的解决方案因为每个行为将会是AUT-specific。尽管如此,对于完整性,我们将展示上面展示的测试数据中使用的所有行为的实现,有一个例外。例外就是 startApplication 函数已经被编译进Squish,因此我们没有必要亲自来实现它。

下面的函数均取自 action.py脚本:

def chooseMenuItem(menu, item):    activeItem(waitForObjectItem(":Address Book_QMenuBar", menu))    activeItem(waitForObjectItem("Address Book.%s_QMenu" % menu,item))

这个函数激活了给定的菜单以及给定的菜单项。我们已经使用了从Object Map 中获取的symbolic names。

当然,Object Map开始是空的,因此在创建任一函数之前首先要做的事情是记录或回放一个虚拟的测试。在该测试中我们做在keyword driven 测试中计划该做的事(不要有重复)。因此我们创建一个新文件,添加一个地址,移除一个地址,退出。这能够在Object Map中定位到大多数我们需要的名字。

menu bar 和 menu option 有不止一个指向它们的symbolic name,因为一旦AUT的状态发生改变,它的窗口标题也会改变,Squish 会通过创建多个symbolic names记录下这个变化,每个symbolic name都携带一个不同的window属性值。这个属性的值根据窗口的标题不同而不同,窗口的标题也在不断变化。我们需要能访问menu bar 和 menu items不管AUT的状态怎么样(特别地,不管它的window标题怎么变化)。

为了解决问题我们使用Object Map 视图来编辑Object Map。首先我们从“:Address Book_QMenuBar”项中删除window 属性,以便能够找到menu bar,不管window的标题是什么。然后由于同样的原因我们从":Address Book.File_QMenu"项中删除window 属性。然后我们拷贝":Address Book.File_QMenu"symbolic name并且粘贴;我们重新命名粘贴的版本":Address Book.Edit_QMenu" 并且从"File"到"Edit"改变它的title 属性值。我们然后删除任何symbolic names包含的“Unnamed”的menu 项。

在编辑了Object Map之后,启动一个菜单及其菜单选项的函数能够完美工作——不考虑AUT的窗口标题。

import __builtin__def verifuRowCount(rows):    test.verify(__builtin__.int(rows) == getRowCount(), "row count")
这个函数用于验证AUT中的行数与期望值一致。

所有的keyword数据都以string存储和获取。对于数据项(type,value)我们当然可以使用两列,或者一些其他的指定方案项目(例如type=value)。但是这加重了测试员处理不同类型的负担。因此我们反而接受一切格式的strings,接受同这里一样的我们需要的其他类型,我们执行AUT-specific函数中的转换。(除了Perl ,在Perl中我们简单的强制转换string)。

对于Python,转换有点复杂,因为Squish导入自己的Python-specific int() 函数,它不同于内嵌的。为了排除这样的故障我们导入Python的 __builtin__ 模块,访问Python自己的 int() 转换函数将rows 字符串转换成一个数字。

def getRowCount():    tableWidget = waitForObject(":Address Book - Unnamed.FIle_QTableWidget")    return tableWidget.rowCount
这个帮助函数获取一个指向基础工具包表的引用(在这里是一个QTableWidget),然后返回它的rowCount 属性的值。在那个虚拟的测试运行定位到了Object Map中之后,表的symbolic name是从Object Map中拷贝出来的。

def addAddress(forename, surname, email, phone):    oldRowCount = getRowCount()    chooseMenuItem("Edit", "Add...")    type(waitForObject(":Forename:_LineEdit"), forename)    type(waitForObject(":Surname:_LineEdit"), surname)    type(waitForObject(":Email:_LineEdit"), email)    type(waitForObject(":Phone:_LineEdit"), phone)    clickButton(waitForObject(":Address Book - Add.OK_QPushButton"))    newRowCount = getRowCount()    test.verify(oldRowCount +1 == newRowCount, "row count")    row = oldRowCount # The first item is inserted at row 0;    if row>0:        row -= 1    checkTableRow(row, forename, surname, email, phone)
这个函数添加一个地址到addressbook应用程序中。开始获取row 数量,然后使用自定义函数 chooseMenuItem 函数执行 Edit|Add... 菜单选项来弹出Add 对话框,然后在合适的line editor中键入每块信息。下一步,点击对话框的OK按钮。一旦对话框已经接受了,再一次获取row数量,我们验证现在比之前多了一行。我们使用 checkTableRow 函数检查每个数据项都被正确的输入到了表中。

在函数最后一个稍微棘手部分是我们要求 checkTableRow 函数验证的行必须小心计算。如果没有地址(它是初始状况),新的地址将会被插入到第0行(第一行)。但是随后的地址被插入当前行的前一行中(当前行始终是之前插入的)。因此第二个地址也被插入到第0行中,第三个地址在第1行,以此类推。这是address 应用程序的一个简单的行为怪癖,我们必须在测试中考虑到。

FORENAME, SURNAME, EMAIL, PHONE=rang(4)def checkTable(row, forename, surname, email, phone):    tableWidget = waitForObject(":Address Book - Unnamed.File_QTableWidget")    test.compare(forename, tableWidget.item(row, FORENAME),text(), "forename")    test.compare(surname, tableWidget.item(row,SURNAME).text(),  "surname")    test.compare(email, tableWidget.item(row, EMAIL).text(), "email")    test.compare(phone, tableWidget.item(row, PHONE).text(), "phone")
这个函数比较每个刚刚被键入text的table单元,以验证它们与键入的是一致的。

def removeAddress(email):    tableWidget = waitForObject(":Address Book - Unnamed.File_QTableWidget")    oldRowCount = getRowCount()    for row in range(oldRowCount):        if tableWidget.item(row, EMAIL).text() == email:            tableWidget.setCurrentCell(row,EMAIL)            chooseMenuItem("Edit","Remove...")            clickButton(waitForObject(":Address Book -Delete.Yes_QPushButton"))            test.log("Removed %s" % email)            break    newRowCount = getRowCount()    test.verify(oldRowCount-1 == newRowCount, "row count")
为了让测试员更容易的定位keyword数据我们提供了一个 removeAddress 函数,该函数使用email地址来识别删除哪一行。(这是基于合理的假设,即address book中的每个email地址是唯一的)

这个函数开始获取一个指向AUT的table的引用,以及当前行数。然后遍历每行直到找到符合email地址的那一行。一旦匹配,将该行设置为当前行,执行AUT的 Edit|Remove... 菜单选项来删除该行。执行这个菜单选项会弹出一个Yes/No 的确认对话框——该函数点击了对话框的Yes按钮。一旦完成删除,退出循环,然后我们验证行数比之前少了一行。

def terminate():    chooseMenuItem("File", "Quit")    clcikButton(waitForObject(":Address Book.No_QPushButton"))
为了终止AUT,我们必须首先执行 File|Quit菜单选项,然后点击弹出的‘save changes’对话框的 No按钮,这样我们就可以不会做任何数据保存的退出。

这完成了对 AUT-specific 函数的回顾。除了 getRowCount 助手所有的函数都被用在keyword数据中。唯一确实的块是驱动函数,该函数将keyword 数据用于调用AUT-specific 函数:我们将在下一小节实现该部分。

Ⅰ.11.3  如何创建一个通用 Keyword Driver 函数

driver.py 文件(或 driver.js ,以此类推),提供了一独立的函数driver,它接受一个keywords 数据文件作为它唯一的参数,并运行数据中执行的命令。

source(findFile("scripts", "actions.py"))def drive(datafile):    test.log("Drive: ‘%s’ % datafile")    for row, record in enumerate(testData.dtaset(datafile)):        command = testData.field(recod, "Keyword") + "("        comma =""        for i in range(1, 5):             arg = testData.field(record, "Argument %d" %i)             if arg:                  command += "%s%r" %(comma, arg)                  comma = ", "             else:                  break         command += ")"        test.log("Execute:%s" % command)        eval(command)
必须要做的第一件事是通过导入 actions.py 文件(或 actions.js,以此类推)访问AUT-specific 行为。

drive 函数遍历测试数据(Squish的 testData.dataset 函数自动跳过含有标题名的第一行)每一行。对于每一行,该函数获取keyword(如,待执行的AUT-specific 函数的名字),然后获取的是参数。在这里我们限制了keyword数据最多有四个参数,但是扩展到更多个参数比较容易。

对于每个keyword 数据记录,我们创建一个由AUT-specific函数组成的命令来调用任何给出的参数。一旦命令准备好了我们记录下待执行的内容,然后运行命令。

这里完成了支持keyword-driven testing的引擎之下的功能。这些工作并不特别的难。driver函数只能被写一次因为它可以用于任何AUT,不管它们使用的GUI工具包是什么(只提供AUT-specific 功能函数在名为actions.py的脚本中或者actions.js中,以此类推)。每个AUT只需要写一次AUT-specific 功能,尽管一些函数对于多个使用了相同的GUI工具包的AUT可能是可重用的。


0 0
原创粉丝点击