对于某个特定程序,为其开发自动化功能测试解决方案的过程,与创建该程序的过程,二者相较并没有很悬殊的差别。自动化测试是一个非常年轻的领域,它正在不断经历大量的进步、提升和标准化进程。在这个领域中,涌现了许多与“被测系统”(SUT,System
Under Test)互动的新工具。
现在,软件开发方面有大量可供选择的方法论和途径,例如:面向对象编程、函数式编程、领域驱动设计、测试驱动设计、行为驱动设计等等。它们拥有明确的声明性概念和理论,并简化了对初始系统架构的定义过程、对系统的理解以及开发者之间的知识交换等方面的工作。
本文将主要针对GUI(图形用户界面)应用的测试自动化进行讨论——从自动化开发人员的角度看,在这种情况下被测系统(SUT)表现为一个黑箱(被测系统,是指一个正在测试是否能够正确操作的系统。对于桌面应用来说,它就是应用本身,而对浏览器系统来说——则代表了网站/Web项目等含义)。在公司的遗留系统占很高比例的环境里,或是在新开发的系统没有考虑可检测质量属性时,这一现象非常常见。
对最佳实践的准备和定义,是开发自动化的测试的关键部分。下图展示了被测系统和测试者之间的传统交互:
测试者与SUT之间的交互
位于该系统中心的,是一个扮演测试者角色的人类个体。测试者使用手动交互和应用的视觉化分析,以及特定的SUT非可视化界面访问工具,将测试用例中所描绘的场景进行复制。如果失败或是遇到系统意外行为,测试者便将错误行为的有关信息输入到默认的追踪系统中。
自动化测试的主要目的,是消除(或者至少最小化)人类与SUT之间的交互。在持续交付产品开发周期中,这是非常常见的问题。一份文献来源的研究表明,现在自动化测试系统的数量非常多。商业产品一般会公布一系列详细的要求和推荐,特别适用于其产品。但是这些厂家往往不会公布适合与任何自动工具一起使用的一系列工具诊断实践。
此外,这些自动工具软件提供商往往还会运用营销手段,基于小量功能测试来描绘其系统的优势。但是随着自动化测试数量的增长,维护现有测试将成为系统工作流程中最昂贵的部分。
自动化测试框架旨在帮忙解决这些问题。他们定义了基本的系统可复用组件,公布了最佳实践和统一自动方法——为了正确地开发自动化测试框架,我们需要得到独立最佳实践的指导。
自动化功能测试的模式
让我们检查以下Web应用程序自动化方面的问题(图1),这是一个自动化解决方案装置的例子。该应用包含了一幅登录页面,而每项测试都必须经过该页面,才能进行更进一步的测试。
图1:示例——拥有最少页面和功能集的简单Web应用
分类体系(图2)为我们带来了全部功能测试模式的整体视角,本文稍后会对各个模式展开描述。
图2:自动化功能测试模式的分类
测试实现的模式
记录
通过自动化测试工具来执行此模式的测试实现。该工具记录并回放手工测试者的操作。某种程度上,考虑到昂贵的维护成本,这种方式被认为是一个糟糕的实践。
脚本化
由程序员使用自动化工具的API来执行测试实现(例如Selenium WebDriver API)。
实现基础模板(测试模板)
该模式将实现基础测试模板类。要实现各种测试的变体,可以通过继承和扩展模板类的功能来创建。
数据驱动实现
通过测试用例来定义一个基础测试的实现。可以通过一系列不同的输入数据组合,来创建各种测试的变体。
在大部分单元测试框架中都实现了这一方法,例如:MSTest以DataContext(键值集合)的方式,提供对测试内部的属性的访问权限。于是,相同的测试方法主体会运行很多次,但DataContext属性所提供的数据各不相同。
关键字驱动实现
这一测试实现借助了关键字(例如:点击、回车)。测试实现通过特殊的IDE完成,这类IDE可以钩挂到应用的UI上。
目前已有数款软件工具,支持借助关键字来实现测试。测试的步骤,以关键字、屏幕上的控制名和输入参数的结合的形式出现。HP
QTP和MonkeyTalk是这类IDE中的典型例子
模型驱动实现
对任何应用来说,在特定的时刻接受特定的输入,都只会有一个特定的状态。根据这样的定义,我们可以将软件程序描绘为有限状态机(有限自动机)。考虑到这一事实,以及状态可用性和转换模型(例如图1),我们可以定义一套特定的页面间转换(工作流)的集合,它将能够覆盖程序的大部分功能。
架构模式
多层的测试解决方案
该模式从逻辑维度将正在测试的系统划分为独立的逻辑层级。
将软件系统以架构性方式分为独立的层级是一个广为流传的实践。第一层封装了表述逻辑,第二层则是业务逻辑层,而第三层负责数据存储。使用这一范式,有助于降低应用维护成本,因为更换每层内部的组件对其他层级的影响将被最小化。而同样的方法也可以运用到测试系统中。
测试代码同样可以分为三层:用来向SUT提供访问的UI自动化工具界面层、功能逻辑层和测试用例层。每一层都肩负特定的责任,而它们都在追求共同的目标——降低测试维护成本,提升创建新测试的便利性。
图 3: 架构性原型——测试系统的多层架构
元框架
该模式定义了一组基础的独立工具类。对任何自动化工具来说,它们都是通用的,而且可以在不同自动化项目之间复用。
当需要在一个组织机构中测试不同的项目,而且企业标准要求测试结果采用统一的界面时,或许就需要这样的解决方案。此外,元框架改进了项目间代码复用的度量标准,因为它可能会包含有用的工具方法。有关功能和测试对象的基础类,简化了项目之间知识的转移。元框架展现在图3的右侧。
功能组合模式
功能方法
针对特定于应用的业务功能,从其UI实现、API或其他层面进行了抽象。
用于自动化测试的许多工具,都支持创建被称之为“场景记录”的功能。当一位测试开发者针对特定应用执行特定操作时,这些工具将自动创建一份测试脚本。它可以在稍后进行回放,并检查在程序发生变化后,该脚本是否得到了正确的执行。
例子:改变登录界面的外观,将要求在全部预计的测试场景中的对应部分都进行变更。如果我们将登录方法提取为Application.Login(username,password),并在全部测试中使用这一方法,那么当登陆页面发生任何变更的时候,我们将只需要修改仅仅这一个功能方法,随后变更就会被自动分发到所有使用该方法的测试中。
图4:测试脚本与(a)缺乏功能方法的过渡层的用户界面之间的交互;(b) 带有功能方法层的用户界面。当应用发生变更时,阴影部分对象将会被改变。
页面对象
这一模式将某个页面的功能方法组合在一起。
由于图1中展现的用于应用的功能方法数量不多,因此它们可以移入一个单独的类。但是为了提升代码可维护性,此模式建议依据这些方法所代表页面,对其进行分组。例如,这些对应关系可以包括:页面:PageLogin——方法:Login();页面PageHome——方法:Logout()、CreateUser()。
功能库
它将某个特定应用的功能对象或(和)功能方法分组打包,成为一个适合复用的模块。
SUT的启动和拆卸对象(SUT运行者)
该模式支持被测系统的初始启动,即它的初始化。在此之后,测试对象释放与该系统相关的资源。
在功能方法之中,我们可以区分出与功能测试无关的方法集合:例如启动某个Web浏览器,并访问SUT的登录页面。而在测试之后,Web浏览器应该被关闭。SUT运行者负责以上这些通用活动。
对象源(对象之母、对象精灵、对象工程)
该模式按测试执行所要求的形式,创建并初始化的对象。
运输者(导航器)
根据测试要求,它将被测试系统中的导航控制集中在一起。
该对象封装了与被测试系统内部的导航实现有关的完整逻辑。因此业务逻辑的问题不会影响系统内的导航。
对于图1所描述的情况,我们将拥有一个Transporter类(运输者),它拥有以下方法:NavigateToLogin()、NavigateToHomePage()和NavigateToCreateuser()等等。或者,每个独立的页面(page)对象或许都会拥有自己的运输(transport)方法。在这种情况下,这些方法就作为该对象自身的运输者。
复合页面对象
该模式将复用的页面(page)对象聚合在一个外部对象中。
这一模式支持用更加“面向对象”的方式来组织页面对象——把可以在不同页面上复用的子对象分离,并将其包含到父对象中。
图5:通过在主页和创建用户页面对象中聚集,来使用导航页面(Navigation
Page)对象
扩展的页面对象
该模式通过继承来扩展基础的页面对象,成为复合页面对象的替代选择。
过程模式
给定条件/时机/结果
此模式将测试执行过程分为三个阶段:
给定条件(定义先决条件)
时机(设定与上下文协同工作的特定操作)
结果(检查结果)
四阶段测试
此模式将测试执行过程分为四个阶段:
定义先决条件
调用业务功能
Checking results;
拆卸系统
流测试
该模式支持在一个测试内执行业务操作并进行检查。两项内容可以交替进行,直到实现测试的最终目标。
多次失败
该模式定义了一套机制,能够在非致命错误出现之后继续进行测试。
测试依赖模式
独立测试
该模式令被测试的系统回到测试前的状态。
链式测试
在该模式中,初步测试树立起了测试需要跟踪的SUT状态。
测试分组模式
每个测试类封装一个测试方法
在该模式下,每个独立的测试方法,都封装在一个独立的测试类中。
测试类中的分组测试方法
在该模式下,多个测试方法被放置于一个独立的测试类中。
总结
测试解决方案的设计背后的驱动力,是对特定测试实现模式的选择。它扮演了未来所有测试解决方案开发的起点,将在可读性、可维护性和其他诸多特性方面产生影响。而且它也设置了这些实验,使其在能够产生帮助的时候更好地复用项目之间的资源,并减少新项目自动启动的时间。
这篇文章抛出了有关如何参考设计模式,来构建测试解决方案的想法。
|