在过去的十年里,使用自动化单元测试套件的做法已经被广泛接受,以至于当前大多数开发人员都会从事一定数量测试代码的编写,或者至少会感觉不写不好。然而 自动化单元测试的不断使用却带来了一些混乱,即谁该测试什么。开发人员是否需要在所有的代码中覆盖单元测试,如果这样做了,是不是意味着我们就不再需要专 门的 QA 测试人员了?许多开发团队在用户界面这块划了界线,他们认为用户界面需要很少的编码或者根本不需要编码,因此可以更经济地让专门的测试人员手工或 是使用专门的测试工具进行测试。这种分工倾向于将测试划分成“单元测试”和“功能测试”,由开发人员负责前者,而由 QA 测试人员负责后者。本文将探讨 Gorilla Logic 公司的全新开源 Flex 用户界面自动化测试工具FlexMonkey,看看它是如何提高开发人员和 QA 测试人员生产力的。FlexMonkey 允许开发者将用户界面测试纳入到单元测试套件和持续集成环境中,还允许 QA 测试人员将那些测试扩展到综合质量测试中。
虽然开发人员和 QA 测试人员的最终目标都是确保应用程序正常工作,但是开发人员测试的首要目标和 QA 还是有所不同的。开发人员测试的目标是为了保证新编写 或者修改的代码能够如愿工作而不导致“回归”(也就是说,无意中破坏了之前工作的代码)。自动化回归测试是敏捷开发中至关重要的一个部分,因为只有疯了的 开发人员才会在大量琐碎的代码重构后,不用足够的自动化测试去确保重构后的代码仍然工作。
我们有理由期待开发人员去测试每一个新编写的代码片段,但是将每一个这样的测试实行自动化则是不必要的。手工测试对于新的或是修改的功能而言非常高效,但 对于防止回归而言又总是不切实际。为了防止回归,开发人员必须为应用程序提供一系列自上而下并且端到端(前端到后端)的测试,另外有证据表明即使自动化测 试少到只覆盖 50% 的代码,它也能在很多应用中有效的防止回归。
QA 测试比起开发人员的测试要来的更加细致,因为它的目标是确保代码能够在任何可以想到的使用场景中正常工作。换句话说,QA 测试人员的任务就是通过在应 用程序上做一些可怕的事情来破坏它。当然开发人员肯定很熟悉这些所谓可怕的事情并且他们自己就可以做这方面的测试,但是这样做会使得他们真正写代码的时间 变得很少。
一个合格的开发人员在测试时要么采用测试优先准则,即在 API 本身被实现之前就创建好测试它的少量测试用例,要么采用写一点,测一点(code-a- little-test-a-little, CALTAL)的准则,即每一个开发出来的代码单元在编译完后都会马上测试。CALTAL 方法在用户界面(UI)开发人员中尤其流行,但是不同于 API 的 在实现之前就能定义好逻辑接口的是:CALTAL 在没有实际实现界面和控制代码前,并没有很好的方法来表达如何测试用户界面。
程序员们一般都主要依靠 xUnit 家族的测试框架来组织和执行单元测试。xUnit 框架,例如Junit和FlexUnit, 可以帮助开发人员管理大量的自动化测试套件。利用这些框架,单个测试套件可以单独的或是组合的运行,就是说为系统的某个部分开发的测试套件既可以独立运 行,也可以作为测试完整系统或整个应用中更大测试套件中的一部分来运行。此外,框架提供的综合报告使得开发人员和管理人员可以轻松的查看汇总和详细的测试 结果。xUnit 测试是本身就可以执行应用程序代码,并且检查实际结果和期望结果是否一致的小程序。xUnit 测试套件提供了一个简便并且高效的方法来防 止回归。大多数的生成系统,如ant,为应用程序生成过程运行将 xUnit 测试套件提供了直接的支持。而持续集成系统,如Cruise Control或Hudson,在每一次代码提交到团队的版本控制系统后都会自动的触发这些编译,并且帮助检查团队中是否有人应该被笑话提交了不能通过测试套件的代码(这些笑话对于提高开发团队的总体效率是至关重要的)。
另一方面,QA 测试人员主要还是依赖于可视化工具。这些工具可以录制与应用程序的交互,如鼠标点击和键盘输入,并且可以在检查结果时回放那些交互过程。过 去这些工具并不能很好的适用于开发人员,因为结果录制脚本通常非常脆弱,只要细微的改动,例如重新布局输入框或是引入了与特定脚本无关的额外界面元素,都 会导致回放失败,虽然界面更新后的应用场景仍然合乎逻辑。因此 UI 开发人员在测试时都趋向于“剥离”用户界面本身,而代之以编程直接调用控制代码或是生成 “合成的”UI 事件。虽然这种方法不是没有好处,但是要精确的复写出与用户界面异步交互的代码却很难。这样带来的后果是需要耗费很多人力开发这种测试,并 且由于用户界面本身并不直接由测试执行,所以这种测试通常不能检验出正确的键盘和鼠标输入处理以及正确的显示数据。因此看到开发人员手工测试用户界面的情 形就很平常了。虽然 UI 开发人员在这种方法中也可以进行 CALTAL 的过程,但是用户界面却完全不需要进行自动回归测试和持续集成测试。
FlexMonkey 是为提供鲁棒的录制和回放功能而设计的,它与商业工具,如惠普的 QuickTest 专业版有点类似,不同之处在于 FlexMonkey 能够同时满足开发人员和 QA 测试人员的需要。FlexMonkey 为用户交互场景提供了交互的录制和回放功能,还附带将测试保存为待 运行的 FlexUnit 测试套件。这些由 ActionScript 组成的测试可读可编辑,甚至可以不录制而通过从头开始编码实现。
交互地创建测试
FlexMonkey 本身虽然是 Adobe AIR 应用程序,但却可以测试 AIR 和 Flex 应用程序,包括使用远程服务如 BlazeDS 在内的基于服务端的 Flex SWF 文件。FlexMonkey 可以在不需要链接任何特殊测试库的情况下,录制和回放应用程序的交互,并且既可以测试单独运行的应用程序,也可以测试在 浏览器中运行的应用程序。
想要创建一个测试套件,你可以使用 FlexMonkey 加载器运行应用程序,方法可以通过文件选择对话框定位到 SWF 文件或者也可以指定要加载的服务器端 SWF 的 URL。FlexMonkey 将加载 SWF 并且将它显示在自己的窗口或者浏览器页面中,它还会打开一个包含FlexMonkey 控制台的 单独窗口。该控制台主要用来管理用户界面测试的录制和修改。想要录制一个测试,你可以简单地点击 Record 按钮,然后与你的应用程序进行交互即可。当你 点击组件或是在应用程序窗口中输入一些字段时,每一个动作都会被录制下来并显示在 FlexMonkey 控制台中。下面的屏幕截图显示了一个简短的录制过 程,其中我们使用了一个简单的联系人管理应用程序(“Monkey Contact Manager”),输入名字(“Fred”)和电话号码(“555 555 1212”), 然后点击 _Add_ 按钮,应用程序联系人列表中就会多出了一个新行。FlexMonkey 可以如实的录制任何一个 UI 事件,包括许多“外部的”事件,例如选择一个嵌入在 DataGrid 中的 ComboBox 里的值。
在上面的屏幕截图中,我们看到在手工测试Monkey Contact Manager 应用程序时,FlexMonkey 控制台包含了录制的事件。
FlexMonkey 使用熟悉的 xUnit 测试层次来将所有的测试组织成测试用例和测试套件。在这个例子中,我们创建了一个叫做MonkeyContactsSuite
的套件,它其中包含了一个叫做MonkeyContactsCase
的测试用例。这个测试用例反过来包含了一个叫做TestAddNewContact
的单个测试,而这个单个测试是由我们刚刚录制完的不同用户操作组成的。对于每一个用户操作,FlexMonkey 会捕获一个UI 事件,一个属性值对,用以标识事件的目标以及事件相关的参数。点击第一个Input操作,我们可以看到 FlexMonkey 控制台右侧显示出它的详细信息,并且可以修改其中的参数。例如,我们可以在回放过程中将脚本中的输入由"Fred"改为"Ethel"。
这个操作将一个带有"Ethel"参数的UI 输入事件发送到了一个属性为automationName 值为inName 的组件中。换句话说,这个操作是指示FlexMonkey 在回放过程中向联系人管理程序中的Name 字段输入"Ethel"。Container Value 和 Container Property 两个字段是通过指定父容器的属性/ 值对来进一步验证事件的目标组件。利用目标和容器属性来查找一个组件可以很明确地引用到任何UI 组件,甚至都不需要指定automationName 属性。例如,我们创建一个这样的操作:点击id 为"editPanel"组件内部标签为"Go"的组件。通过点击FlexMonkey 控制台上的Verify 按钮(绿色的勾)打开FlexMonkey Spy Window 窗口,我们可以交互性的创造一个断言来验证一系列的属性值对。使用这个spy 窗口,我们可以选择UI 组件上任意的属性集合并检验它们是否与想象中一样。下面的屏幕截图展示了如何使用spy 窗口,来验证一系列操作后Name 字段的text 属性是否为"Fred"。
除了验证属性值以外,FlexMonkey 还可以在录制的过程中捕获应用程序窗口特定选择区域的位图,并且可以在回放的过程中对比这块窗口区域,这相当于提供了一个自动“目测”应用程序在实际屏幕显示时某个部分的方法。
我们可以点击Play 按钮来运行我们的测试。过程中FlexMonkey 回放了每一个在联系人管理应用程序中记录的操作,而应用程序的响应方式与手工操作键盘鼠标时完全一样。我们的简单测试运行结果如下:
通过这种方式使用FlexMonkey,我们可以交互性地创建和维护多种多样的,复杂的,可以包含任意数量测试用例和测试的套件。
使用ActionScript 生成测试
除了交互性的创建和与运行测试外,我们还可以使用 FlexMonkey API 编程指定与 xUnit 框架如Fluint( http://fluint.googlecode.com )和FlexUnit 4( http://opensource.adobe.com/wiki/display/flexunit/FlexUnit )一起运行的测试。
尽管可以使用 FlexMonkey API 完全地从头开始创建测试,然而我们其实使用 FlexMonkey 控制台来直接从以前录制的测试场景中创建基于 ActionScript 的测试。选择 “生成 AS3”菜单选项来为每一个套件和测试用例生成 ActionScript 类。每一个独立的测试都会导出成它对应的测试用例类中的方法。下面是我们的简单示 例所生成的测试用例类。
package testSuites.MonkeyContactsSuite.tests { import com.gorillalogic.flexmonkey.events.MonkeyCommandRunnerEvent; import com.gorillalogic.flexmonkey.core.MonkeyTest; import com.gorillalogic.flexmonkey.monkeyCommands.*; import com.gorillalogic.flexmonkey.application.VOs.AttributeVO; import com.gorillalogic.flexmonkey.flexunit.tests.MonkeyFlexUnitTestCase; import mx.collections.ArrayCollection; import flash.events.IEventDispatcher; public class MonkeyContactsCase extends MonkeyFlexUnitTestCase{ public function MonkeyContactsCase(){ super(); } private var mtTestAddNewContact:MonkeyTest = new MonkeyTest(null, 'TestAddNewContact', null, 500, new ArrayCollection([ new UIEventMonkeyCommand('Input', 'inName', 'automationName', ['Fred']), new UIEventMonkeyCommand('Input', 'inPhone', 'automationName', ['555 555 1212']), new UIEventMonkeyCommand('Click', 'Add', 'automationName', ['0']) ])); private var mtTestAddNewContactTimeout:int = 8500; [Test] public function TestAddNewContact():void{ // startTest(<MonkeyTest>, <Complete method>, <Async timeout value>, <Timeout method>) startTest(mtTestAddNewContact, TestAddNewContactComplete, mtTestIA1TimeoutTime, defaultTimeoutHandler); } public function TestAddNewContactComplete(event:MonkeyCommandRunnerEvent, passThroughData:Object):void{ checkCommandResults(mtTestAddNewContact); } } }
测试中的每一个操作都生成出了一个 UIEventMonkeyCommand。每一个命令都指定一个操作,属性 / 值对用以标识该操作以及操作相关参数所对应的目标组件。(所有可用的操作都在 http://code.google.com/p/flexmonkey/wiki/FlexMonkeyCommand 有 记录。)在这个例子中,每一个 UIComponent 都使用了 automationName 属性来标识,但正如上面提到的,我们可以使用任意的属性值对来替代。我们可以很轻松的修改这些脚本而无须重新录制。例如,我们可以通过将下述命令加入到数 组中来实现在联系人管理中的 ComboBox 下选择电话类型为"Mobile"。
new UIEventMonkeyCommand('Select', 'inType', 'id', ['Mobile'])
我们可以尝试用编程的方式向我们的管理程序中加入 1000 个联系人。例如,下面的几行代码相当于不断地输入一个唯一的联系人姓名并点击 add 按钮:
for (var i:int=0; i<1000; i++) { mtTestAddNewContact.addItem(new UIEventMonkeyCommand('Input', 'inName', 'automationName', ['Contact' + i])); mtTestAddNewContact.addItem(new UIEventMonkeyCommand('Click', 'Add', 'automationName', ['0'])); }
对于每一个生成的测试方法,都会有一个对应的 Complete 方法。这个 Complete 方法会在测试方法结束后被调用。我们可以在这个方法中附加一些断言来测试其他的属性。例如,我们可以检查脚本完成后列表中第一个联系人是不是"Fred"。
public function TestAddNewContactComplete(event:MonkeyCommandRunnerEvent, passThroughData:Object):void{ var grid:DataGrid = MonkeyUtils.findComponentWith("grid", "id"); // Find the component with id="grid" var actual:String = grid.dataProvider[0]["name"] assertEquals("First contact was not Fred", "Fred", actual); }
我们还可以用这个方法测试非可视化应用程序类中的值。
我们用 FlexMonkey’s GUI TestRunner 加载待测试的 SWF 和本身就包含测试的 SWF,然后运行测试。
虽然我们举的例子有点过分简单,但还是可以很清楚地看到,FlexMonkey 利用它强大而又灵活的代码生成功能和定制功能为用户界面场景的录制带来了方便和效率。
UI 测试的持续集成
FlexMonkey 提供了一个可被 ant 任务调用的测试运行器,它的输出结果可以与 FlexUnit 测试混在一起,就像 Java 的 Junit 测试一样。使用 Junit 的 junitreport ant 任务,FlexMonkey 的测试输出可以输出成熟悉的 Junit 报告样式,如下:
FlexMonkey ant 任务首先运行 FlexMonkey 来加载待测试的 SWF 以及本身包含测试的 SWF。输出通过套接字发回到 FlexMonkey ant 任务,并被写入磁盘,这使得它们可以与其他 Fluint/FlexUnit 或者 Junit 测试输出融合在一起,来为套件中所有的 Flex 和 Java 测试提供综合报表。
由于 FlexMonkey 可以从 ant 中运行,因此它可以轻松地集成到使用诸如 Cruise Control 或者 Hudson 框架的持续集成编译中来。
使用 Monkey 进行自上而下的测试
通过将录制器、代码生成器和一个简单易用的 API 结合在一起,FlexMonkey 可以让开发者在整个开发周期中,为每个应用程序功能创建可靠的自动化用户界面测试,而不用随着用户界面的不断改变而变化。在 Flex 应用程序中,用户界面与大部分操作紧密联系,FlexMonkey 自上而下的测试方法为低层次应用类单元测试提供了一个强大的补充。通过创建测试来执行用户界面,开发人员可以使用最少数量的测试用例来测试最大数量的代码。事实上,通过在一个与远程后端交互的应用程序上运行 FlexMonkey 测试中,我们可以看到 FlexMonkey 能够进行从用户界面到数据库乃至外部系统端到端的测试。在持续集成环境中包含这些测试可以为系统提供最大限度的保障,每一处代码改动都不会无意间造成破坏,它们会作为应用系统的一部分而独立运行。
开发人员测试 vs QA 测试
虽然一个开发人员可以使用 FlexMonkey 来创建覆盖所有代码的测试而完全不需要 QA 测试,但最好还是根据特定技能进行分工,让开发人员处理有代表性的应用程序功能,而把大量需要完全覆盖代码,并且需要在应用上做可怕的事情来看看它是否支撑的住的测试交给 QA。FlexMonkey 通过提供了一个通用的测试平台满足了开发人员和 QA 测试人员的需要,这个平台可以让开发人员先行创建测试,并为后续 QA 人员更加细致的测试提供铺垫。
尽管 QA 测试囊括一切的性质,有些时候使得它们太脆弱而加入不到持续集成套件中,但还是有可能将它们集成到持续集成环境中的。不管怎样,选择将 QA 测试加入到集成套件中,可以使得开发人员轻松的建立完整的基础测试。
入门指南
你可以在 http://flexmonkey.gorillalogic.com 上找到关于 FlexMonkey 源代码,二进制文件和完整文档的链接。Gorilla Logic 公司还提供了培训和咨询服务,帮助你入门用户界面的自动化测试以及快速的建立全面的持续集成环境。你还可以关注该项目的 blog: http://flexmonkey.org 。
关于作者
Stu Stern 是 Gorilla Logic(一个专注于 Flex 和 Java 应用程序开发的公司)的总裁兼首席执行官。在 2002 年成立 Gorilla Logic 之前,他曾是 Sun 微系统公司全球 Java 咨询服务的副总裁。并在加入 Sun 之前,他曾是 PaineWebber 公司(现在的 UBS)Equity Trading Systems 的副总裁。尽管职业生涯早早地进入了管理层,但是他还在一直不断的编写代码。你可以在 http://stu-stern.blogspot.com 上阅读 Stu 的 blog。
查看英文原文: FlexMonkey brings unit testing to Flex user interface developers 。
感谢霍太稳对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论