上下文驱动的自动化测试方法(三)
作者:James Bach,Michael Bolton
审稿人:Tim Western,Alan Page, Keith Klain, Ben Simo,Paul Holland, Alan Richardson,Christin Wiedemann, Noah Sussman...
【之前我们翻译了 上下文驱动的自动化测试方法(一)和 上下文驱动的自动化测试方法(二),这是第三部分,也是最后一部分。作者先给两个生动的例子,帮助我们认识“传统的自动化测试”和“上下文驱动的自动化测试”的区别。最后,作者讨论了几个主题:
我们发现了错误,但不是因为自动化的检查;
为什么基于GUI实现自动化交互如此困难?
自动化是一种策略,不应该是一种仪式
以此作为总结。 】
案例二: 通过模式数据生成的工具支持,以获得更好的覆盖和强大的oracle
James经常使用工具来生成特殊的测试数据。他最喜欢的一种策略叫做“简化数据Oracle”(译者注:指test oracle)。他用FooScript来测试场景列表和过滤函数。场景列表是一个允许作者更容易地浏览、选择和移动场景的特征。一个场景是由一个特定的文本标记所界定的一个文本块,例如:“##”
如果我们要测试一个场景清单,我们需要一个包含很多场景的文档。这很简单,我们创建一些文本并将一些场景用分隔符隔开并放入其中,如图5所示。这个文本将如图6显示场景列表。看起来还不错吧?这正是我们想要看到的。
现在我们可以将其作为自动化的典型输出检查(译者注:上下文驱动流派甚至把“check”只是检查,而不是测试,测试是智力活动的探索。这和朱少民老师提出的测试新公式类似,但不同的是,后者把“检测”归为测试活动之一:软件测试的一个新公式引起的思考 )。但是工具可以为我们做更多的事情。因此,James决定编写一个程序,它将创建数千个场景,并以特定的方式识别它们,帮助跟踪它们是否在场景列表中处于正确顺序。换句话说,它是压力测试和正确性检查(由一个真实的人执行测试)的组合,这将帮助我们查看场景列表的显示和序列化场景是否有bug。它帮助我们测试场景分隔符处理各种不同的分隔字符串(因为场景分隔字符串是可配置的)以及场景过滤的功能。
他编写的程序的结果在图7中所示的文件中显示,该文件包含了总共5050个子结点。如果场景分隔字符串被设置为“场景”,那么场景列表将显示如图8。
现在,通过只查看 “07.”的过滤,它应该只挑选出7个场景,无论它们在文档的哪个位置,并在场景列表面板中显示它们。事实也是如此。
这是一种从工具中获得巨大提升的测试。测试人员可以执行实现。测试人员可以移动场景、筛选、编辑文档或其它内容。与此同时,其测试覆盖将会逐步深入,且由于这种模式化的数据,其预测将更加精准。测试人员不局限于使用数据,还可以编辑“创建数据的程序”以创建更有趣的数据。事实上,你所见的上面这个版本的数据,是原始设想的第5次细化改进之后的结果。
案例三:自动化检查
我们想要演示全自动检查,同时保留FocusWriter示例。 FocusWriter没有提供用于测试的API(接口),这要求我们通过GUI实现自动化,因此令人头痛的问题开始了。
更高层次的“检查”概念很简单:在FocusWriter中执行一系列操作来触发几个功能、以几种不同方式更改文档,但结果输出应该与我们在开始时预期的相同。一种检查以初始状态结束的检查方式我们称之为幂等方法(注:Idempotency,任意多次执行所产生的影响均与一次执行的影响相同)。幂等性是一种有用的启发式方法,因为该过程应可重复多次,而不考虑系统状态的任何渐进性问题 ,如果系统状态干扰自动化,那将是一个有趣的测试结果。
James提出了一个自动化的流程:
删除所有旧的临时测试文件
启动FocusWriter
加载Three Musketeers (三剑客)文本文件
搜索并用“〜”替换所有小写字母“e”的实例(因为它没有出现在文本中而被选择)
将文件保存为类型ODT
关闭文件
关闭FocusWriter
打开FocusWriter
打开.ODT文件
用小写字母“e”搜索并替换“〜”的所有实例
另存为新的TXT文件
将原文与新的文本文件进行比较
记录结果。
退出FocusWriter
这样就执行了下列操作:保存(两种文件)、加载(两种文件)、启动、停止、搜索和替换。由于Three Musketeers小说的文字量很大(130万字),也就包含了一些压力测试,但主要作为一个完整性检查还是非常有用的。
在James的建议下,Michael开始使用AutoHotKey(一种类似Visual-Basic的Windows脚本语言)解决任务。他很快遇到了一个问题:他无法查询和确认用户选择文件类型的列表框控件的状态。这个问题和他对AutoHotKey的不熟悉促使他切换到Ruby,他对Ruby的使用有良好的经验。
Ruby有几个为Windows API提供支持的库。他很快就发现了RAutomation库,RAutomation库被称为“一个小且易于使用的库,用于帮助windows自动化控制及其测试”。像许多这样的开源库提供的资料很少,但Michael相信自己经过几分钟的实验就能够理解并将其付诸于实践。
RAutomation被证明是直观且易于使用的,但是Michael很快发现了FocusWriter的某些方面使得自动化过程变得棘手。除此之外,FocusWriter所实现的列表框,使RAutomation针对当前文件类型无法确定当前选项。Michael必须通过其他手段跟踪这一点。保存文件有时会导致确认对话框出现,有时不会。几个对话框共享相同的标题(“Question”),尽管其中的提示和选项不同。脚本通常会在应用程序准备好之前初始化操作,这需要等待一个或多个状态。所有这些都需要实验、发现、学习和修改且不断反复,结果,这将花费数小时。除此之外,Michael真希望自己参与FocusWriter的开发过程,从而能呼吁给FocusWriter更好的可测试性。
在摆弄了很多东西之后,Michael成功地让自动化测试可靠地运行起来,但是当James在自己的系统上使用相同的脚本时,又无法找到并启动FocusWriter!我们一起经过了一个小时的调试分析,最终还是放弃了Ruby。
到目前为止,我们考虑了各种问题。也许我们需要的是一个通过GUI与产品交互优化的工具。我们已经从测试人员那里听说过惠普的UFT(Unified Functional Testing,译者注:现在已经被Micro Focus收购了)及其之前的产品(如QTP)。惠普说:“HP UFT软件通过一种直观的、视觉化的用户体验,在一个IDE中集成了手工、自动化和基于框架的测试。这种远见性的解决方案显著降低了功能测试过程的成本和复杂性,同时持续提升了质量。”为了验证这一说法,我们下载了试用版。UFT的录制和回放功能使用了整整一个小时之后,我们可以使FocusWriter启动,但是不能让UFT识别应用程序窗口。它认出了Notepad,但似乎有一些FocusWriter的东西(事实上,它是用QT工具包建成的?)屏蔽了惠普工具。UFT能够录制一个脚本,但之后无法运行所录制的脚本!我们更改了设置,并以不同的方式编辑了脚本,但都无济于事。
也许再过5分钟或5个小时,我们就能克服UFT的问题了。我们都不擅长使用这一特定的GUI自动化工具,而且也缺乏克服这些障碍的经验。也许我们的编程技能会加快学习曲线。然而,像这样的工具通常是在“没有编程技能的测试自动化”的条件下销售的。这里有一个典型的例子:“测试自动化减轻测试人员的挫折,并允许测试执行,而不需要用户交互,同时保证重复性和准确性。”
其实20年前就有这样的说法。与此同时,无人驾驶的飞行汽车(flying cars)仍然在地面上行驶,并且驾驶员还在方向盘后面。
我们做了最后的努力,James使用AutoHotkey录制了一个执行基本脚本的宏。并且成功了!
我们发现了错误,但不是因为自动化的检查
在检查的过程中,我们发现最终的TXT文件与原来的文件不匹配。这就是检查所能做的事情: 报告某种不一致的情况,然后进一步调查。
我们随后对不一致性的调查发现了两个错误:
FocusWriter以Microsoft Word声称无效的方式编写ODT文件(尽管它显然能够恢复内容)。
FocusWriter不正确地读取ODT文件,导致一额外的行和十个新空格被插入到任何一个以至少两个主要空格开头的行前。
我们已经弄清楚了第一个问题,是因为我们认为使用Microsoft Word和OpenOffice作为“挺不错的产品”,作为对利用FocusWriter所保存的ODT文件进行质量评估、衡量的标准。我们通过一系列工具使我们得到了对第二个问题的理解:
WinMerge用来分析各个文件下的文本内容之间的差异
Frhedit用来分析各个文件的十六进制码之间的差异
Perl用来创建和修改拥有各种不同属性的测试文件,以验证我们的假设
7zip和notepad++用来检查ODT文件的XML内容
Excel用来建立一个表格,用于预测大小变化
维基百科/谷歌 用来研究UTF 8编码
注意,自动检查与发现、调查这些错误没什么关系。它没能省下我们多少时间。就此而言,到目前为止,对自动化检测的投入看似毫无回报可言。自动化检测本身好像是以一种非自动化的方式测试——由一个有着思想的测试人员编写指定测试方案,并在自动化测试的过程中与其进行不断交互——自动检查仅仅给我们提示可能存在的问题。然后,熟练的测试人员(在工具的帮助下)进行检查,系统地将所指出的潜在错误追踪到问题发生的最关键之处。
尝试自动检查所花费的时间比前两个案例多得多。我们从开发这种自动检查的过程中学会了一些东西,可能会使进一步检查变得更容易一些,特别是在我们有经验、产品有更好的可测试性的情况下。但我们想知道:如果我们想要创建一个检查库,这种检查的价值是否与开发它的成本匹配?那么维护成本呢?还有机会成本又如何呢?
可能在将来某个时候,另一个缺陷会蔓延到这个产品,并且特定的检查会注意到它。如果将来产品的这一领域发生了变化,很可能导致自动检查的失败,不管失败是由于产品中的错误还是脚本中的错误。如果这个业务没有变化,自动检测就不会失败,但可能也不值得运行。通常情况下,筛选出值得自动化的测试部分往往很困难,并且这些检查结果常常是无用的。这其中有很多问题与猜测风险和变化相关。这包括技巧、实验和学习。
为什么基于GUI实现自动化交互如此困难?
在听完这个标题后,一些“测试自动化人员”可能会声称他们没有这种类型的问题,如果他们遇见了,他们相信自己能很容易地解决问题。不过这样的话,他们将彻底错失以下几点。
GUI界面是为身体健全的人设计的,他们不会在与产品互动时遇到像机器人一样的问题。事实上,我们的工具也是此类问题的受害者,就像那些身患残疾的人们,却仍然试图使用、享受现代科技。对于人和工具来说,可访问性是计算中的普遍问题。 正如一个可访问产品的成功并不能否定一般存在可访问性问题,仅仅指出一个无故障地使用工具来控制应用程序,也不能反驳我们的一般主张,即:
GUI级的脚本是需要技巧的、是挑剔的。它会在任何时刻以任何方式崩溃。这是个臭名昭著的问题。不仅要学习应用程序和工具,还需要学习它们如何交互。我们从80年代和90年代的历史里学到了这个,我们知道它发生过,而且它现在还在发生。
除了可访问性问题,还可以再想想。甚至像保存文件这样一个人就可以轻松搞定的、简单的事情,在你试图利用代码做同样的事情时,就变成纯粹的专业性任务。当你保存一个文件时,应用程序可能会、也可能不会使用特定方法弹出一个明显的对话框; 它可能用、也可能不用那些正在使用的测试框架的库或控件。在任何给定的运行中,应用程序可能也可能未被指向正确的目录;它可能会问你是否真的想要覆盖另一个文件;它可能不会拒绝继续执行,就算文件被其他进程锁定,或者因为用完了U盘空间;它有可能、也有可能不会在运行中被另一个程序打断;它可能会或可能不会花费不正常的长时间来保存。
人类可以从容地接受这些可能发生的事情——我们几乎没有注意到它们,除非它们看起来是错的! 程序不是这样。任何未被预料到并且没有明确编程的可能性都是脚本运行的、潜在的绊脚石,这意味着对试图编程它的人进行另一轮故障排除、调试和测试。 即使您认为使用您最喜爱的工具可以比使用FocusWriter做得更好,声称这些突出显示的问题在工具中并不常见,并且它们与GUI级用户模拟工具相关,无论您的技能水平如何 、行业或产品类型。
虽然Michael成功地使用了Ruby, James也成功地使用了AutoHotKey,但没有更多的工作,成功似乎只体现在那粗糙的原型脚本中。其实这些都非常脆弱。应用程序、或保存的状态、数据集等最小的更改,都可能会以需要调优或完全重写的方式打乱现有脚本。
您可以让GUI检查在产品更改时更有弹性,而且价格仅有……
作为我们的审稿人之一——Ben Simo指出,面对变化,我们当然可以使GUI检查不那么脆弱,但是使它们不那么脆弱的因素通常也会降低脚本的能力,因为我们通过牺牲某些特性来实现更大的弹性、敏感性。例如,我们可能会过滤掉时间戳或用户名、或者屏蔽截图的某个部分。当我们使用名为“TestRobot6”或“TestRobot22”的帐户运行测试并且想要在两种情况下使用相同的逻辑来检查屏幕时,但是,如果有一个未被发觉的代码缺陷——用户的名称被错误地显示,那该怎么办?我们修改过的检查脚本不会发现它。在检查代码中添加这样的特殊情况逻辑也会增加代码的复杂性,从而产生不同类型的脆弱性:当尝试改进代码时,我们将增加破坏代码的可能性。
我们可能完全成功地抑制了GUI自动化的某些干扰,但同样的干扰可能会为人类、用户提供有关产品的信息。作为人类的我们,很容易理解这些信息具有重要意义。例如,脚本可以在检查进程的输出时实现时间延迟,以便在尝试处理输出之前确保进程完成,但这意味着——我们不会注意到它的响应是否逐渐变慢和在那个处理过程所延迟的时间。测试人员不只是麻木地观察世界。真正的测试人员不只是闲置的产品访客(idle product tourists) - 我们会批判性地分析所看到的东西。但是,如果我们将“视觉”外包给计算机,我们就不能批评它看到的东西,而计算机也不知道如何评判到底哪些内容比较关键。
当您处理这些问题时,您可能会陷入一种更隐蔽的问题: GUI级别的测试会扰乱测试人员进行更深度彻底的测试,以来检查复杂的逻辑的正误与一些功能上的微小的问题。这是因为您必须保持检查的简单性。如果你把复杂的、有趣的数据和交互放到你的检查中,你将会给自己在编码与维护中带来巨大的麻烦。想象一下,在代码形式下,只需要操作五分钟你的计算机。这就是为什么GUI检查设计者关注表面交互和容易解析的输出的原因。这可减少了一笔达到可以看得出的程度的预算支出。这套测试编码将允许您向“测试套件”中添加更多的“测试用例”,但它所完成的可能是浅层测试。同时,在这些投机中省下来的开销,都会在将来占据你大量的将经历与时间以进行更深,更彻底的测试。
当您处理这些问题时,您可能会陷入更加阴险的模式(insidious pattern):GUI级别的检查会分散测试人员进行深度测试——检查复杂或微妙的功能行为。这是因为你必须保持“检查”简单。如果您将复杂、有趣的数据和交互融入检查,您将给脚本开发和维护制造巨大的麻烦。想象一下,以代码形式再现典型的计算机使用时间只需五分钟。让人惊讶!这就是GUI检查设计师专注于表面交互和轻松解析输出的原因。它在经济上是可行的。它将允许您向“测试套件”添加更多“测试用例”,但它实现的可能是浅层测试。与此同时,所有这些努力都会带来机会成本,让您无法进行更深入的测试。
在某种情况下,自动检查可能更便宜、更强大。使用可测试性构建的产品——可编写脚本的接口和可轻松解析的日志文件——往往更易于自动检查。具有更简单输入和输出形式的产品更易于以编程方式进行检查。更靠近开发人员当前任务的自动检查可以提供快速的变更检测、快速反馈和更简单的修复。精心打造的“无缺陷的(unbuggy)”产品可以更容易实现自动化和检查。
我们提出的这些观点并不新鲜。第一届软件测试Los Altos研讨会——还没正式称为“软件测试上下文驱动流派”的第一次有组织的聚会,就致力于解决基于GUI交互进行自动化测试的难题——那可是在90年代后期。
自动化是一种策略,不应该是一种仪式(ritual)
在上下文驱动的世界中,我们拒绝仪式或传统习惯,我们拥抱解决问题的方法。 但这种态度只有在解决问题时才有效。 很多时候,自动化(工件和企业中,都有感受)都被视为一种无可争议的好东西, 即使它完成度很低,也能给人们一种眼花缭乱的错觉。 大型工具公司和咨询公司也没有太大帮助:帮助您看到更简单、更便宜、更灵活的做事方式,这并不符合他们的利益。
如果你的测试并不重要,除非作为公关目的的展示,也许仪式是可以接受的。但是,如果你的目的是在为时已晚之前找到重要的错误,那就不可能。 为了完成这一使命,您必须了解各种工具及其在工作中的应用。 上下文驱动的测试人员以强大的方式应用工具来完成测试!