自动化测试是指软件测试的自动化,它是把以人为驱动的测试行为 ( 用例 ) 转化为机器执行的一种过程。在当今较多软件开发项目特别是敏捷项目中,自动化测试越来越被广泛地被推荐和运用在其中。它具有提高测试效率,缩短测试工作时间,提高测试覆盖率,执行手工 测试不能完成的测试任务以及更好地重现软件缺陷的能力等优点。自动化测试项目实践过程中,新测试脚本的开发以及重用等往往受制于诸多问题,包括:
下文就 Rational Functional Tester(RFT)框架中,如何快速开发新的测试用例脚本,实现脚本及其相关文件的自动化生成的问题,提供一种解决方案。
首先来了解一下 RFT的常用框架,它是 IBM 提供的一款自动化测试工具,适用于各种测试工作,特别擅长于 Graphical User Interface (GUI) 方面的自动化测试。它在 IBM 公司内部的各种测试项目,例如 FVT、BVT、GVT,特别是 REG 得到了广泛的应用。
当被测程序界面发生变化时,用户必须去更新相关的每个对象映射文件。测试用例数量越大,维护工作就会变得更繁重。为了解决代码重用的问题,我们需要应用 RFT 提供的 IBM 三层架构(又称 ITCL)代码结构。
RFT 三层架构包括:
应用对象层:该层包含了以被测程序用户界面元素逻辑关系组织的类。这些类又基于每个更基础的类,比如按钮和文本框。这些类将 RFT 的测试对象完全封装起来,并使它们能被上层代码重用。
任务层:该层包括以被测程序功能组织的类,这些类中的函数通过调用应用对象层的类,能够执行连贯的操作步骤以实现某些功能,并能被多个测试用例使用。
测试用例层:该层调用应用对象层和任务层的代码,按测试用例的要求执行测试步骤,验证测试结果和输出日志。
以下笔者以参加的一个竞标类网站的竞拍系统测试为例,在该例中自动化测试用例要求用户按下竞拍按钮,输入竞拍价格,最后点击提交按钮来完成竞排(如图 2 所示)。本文将结合图及代码实例具体说明如何清晰分离应用对象层,任务层以及测试用例层,以此对 RFT 三层架构作简要介绍。
应用对象层存放的是 GUI 元素,其方法将被任务层调用。在设计应用对象层时,本项目将所有的 GUI 元素及其属性封装在 ObjectRepository 文件下,因为如果将其统一封装进应用对象层,任务层就避免了与 GUI 元素直接关联,并且当 GUI 元素属性发生变更,引起其操作方式的变化时,只需要维护应用对象层即可。
// Place Bids public static final String bidImageIcon = "desc= bid image icon on supplier status area,.class=Html.IMG,.alt=Place Bid"; public static final String PPUTextBox ="desc=Text box to enter the Dscount,.class=Html.INPUT.text,.name=HeaderColumnDiscountDialogControl"; public static final String submitBidButton = "desc=Enabled Submit Bid Button on Bidding Area,.class=Html.SPAN,.className=buttonDeclineUp";
由于以上应用对象层中已经把页面中 GUI 元素的操作封装好,在任务层只需调用应用对象中的操作即可,不用关心具体的对象的类型和属性。示例 2 将调用应用层 GUI 元素操作组合使之形成了一个完整的竞拍报价流程。
其中可以看到任务层的实际代码仍然分为了不同的层级:与业务逻辑无关的通用方法(task.setText())、与业务逻辑有关的方法(示例方法)。如何快速理解和复用这些已存在的任务层代码,是开发者面临的一个重要问题,即本文要讨论的部分。
// Place Bids public void placeBidsforSuplier(String ppu){ task.WaitForBrowser(); task.clickButton(ObjectRepository. bidImageIcon); Step.Sleep(shortTO); task.setText(ObjectRepository. PPUTextBox, ppu); Step.Sleep(shortTO); task.clickButton(ObjectRepository. submitBidButton); Step.Sleep(shortTO); }
测试用例层,只需调用应用对象中的操作即可,验证测试结果和输出日志。示例 3 为最后的测试用例脚本。
// Place Bids public void testMain(Object[] args) { placeBidsforSuplier(“100.00”); }
本文介绍的实践经验可以看作一个用例脚本自动生成模块。脚本自动生成模块利用 RFT 框架的应用对象层及任务层的数据,生成测试用例层的文件,包括测试脚本及测试数据,其本身作为一个功能模块嵌入在 RFT 项目框架中。该生成模块的核心组件在于找到或生成测试用例需要的任务层 Methods,交由用户配置并生成用例和数据文件。实现过程均使用配置文件(本文使用 Excel 文档)和生成模块共同完成。下文中会分别说明两种任务层 Methods 的实现方式,然后介绍如何生成测试数据,生成测试用例脚本的过程会在“实例演示”中说明。
复用历史任务层 Methods:这种情况应用于本身历史代码库可以完成新的脚本需求,在实际开发过程中应用十分频繁。用户只需要在配置文件中配置需要的功能关键字,使用生成模块在任务层中遍历搜索,找出所有相关的 Methods,并选择使用相应的 Method。
新增任务层 Method:当搜索不到历史 Methods 时,就需要新增任务层 Methods。生成 Methods 的方法有很多,例如通过录制回放的脚本来实现与代码的对应。本文的实践过程中业务过程相对比较复杂,自动生成一个完整的 Method 实现率低,因此这个过程仍然保留手动完成。但是在新增 Method 的过程中,发现各个应用层对象 Profile 的文件配置过程虽然简单,却十分繁琐,经常发生相同对象配置多次的问题。为了解决这个问题,用例生成模块提供一个简单的配置模板,实现对象层部分的自动化配置生成,将开发者的精力集中在任务层的设计上。
对于维护自动化测试脚本的测试开发人员,经常需要增加新测试用例脚本,或修改旧的测试脚本以添加新的逻辑。在这个过程中可能复用之前已经写好的 Task Layer Methods, 以节约时间,最重要的是避免代码的冗余,便于代码维护。RFT 的三层框架细分后,在代码结构上,Tasks 层会按逻辑、UI 等维度细分成更多的文件夹、文件。如何从数量繁多的 Tasks 层文件中找到自己需要的方法(Methods)是测试开发人员首先面对的问题。
用例脚本自动生成模块通过在 Tasks 层遍历所有的文件,找到与目标方法相关的所有方法及其用到的参数信息。测试开发人员可以通过提前配置需要的方法关键字、需要遍历的 tasks 层文件夹,来获得需要的分析结果。这种遍历的方式有两个好处:第一,可以进行包括方法名、参数、注释在内各种文字分析,在更广的维护搜索需要的方法;第二,可以精确的读取方法里使用的参数,让测试开发人员快速判断它是否是自己需要的方法。
本文的配置内容以及搜索到的信息都记载在 Excel 里。在 RFT 项目中,Task Layer Methods 大部分位于同样的结构下,如下图所示。将文件夹的目录记到配置文件的相应位置,使用文件遍历与 Java 反射相结合的方法,取到每个文件里包含的方法、属性内容,将其存储到 List 文件里,以用来筛选最终需要的方法。
首先,文件遍历方式实现对 Tasks 层下所有文件(本质都是 .class 文件)的分析,读取每个文件里的所有 public 方法,public 属性, public 子类(及其方法、属性)。其中读取的 Methods 不仅包括方法名,还包括其参数、注释信息。另外在某个 Methods 的参数获取过程中,不只是简单的取得开发者定义的方法类型,而是分析参数本身的属性,针对自定义对象类型的参数,通过遍历方法体获取自定义对象里被使用到的具体参数,这个过程通过使用 Java 反射的方法来完成。
如下图所示为通过一定的配置读取到的方法信息,方法名和所使用到的参数都可以完整的展示。图中所示为“create scenario”这个逻辑搜索到的所有 Methods。第一个 Method 只有一个参数且为自定义参数,要得到该 Method 的参数体,需要遍历该 create scenario 方法体,找到该对象实例“sd”所使用的所有对象内属性(通常以 sd.getXXX(..) 的形式出现)。由于我们生成的 excel 模板除了供用户查看,还可以做为用例脚本生成模板,因此对于自定义参数的处理为:直接取它的“设置”方法,供用户选择及设置(使用方法将在“实例演示”一节中说明)。这里使用 Java 反射的方式,取得该自定义对象参数里的所有方法(get,set)及其子对象,通过对比即可以获得对象内属性的 set 方法,如图中所示 setScenarioName(String) 方法。有时自定义对象内还存在有子对象,如果使用到了子对象的属性,就用递归的方法找到其子对象的 set 方法,例如 bc.setCreateBusinessConstrints(xx) 里面 bc 即为自定义对象内容子对象 BusinessConstraints 的实例。
最终测试开发人员可以得到在给定的 Tasks 层里,自己期望的 Functions Keys Words 对应的所有方法,并根据自己需要实现的功能,结合给定的参数来初步筛选或决定自己可以复用的 Task Layer Methods。例如 Key Words: define bidding rule 可以找到三个同样名字的 Method, 位于不同的文件内,一个只有一个 String 类型的参数,另外两个是自定义参数 EventDetailsObject。通过对比发现,自定义参数的两个 Method 里,有一个使用的对象内参数只有 5 个,另外一个则很多。当我们需要连续完成多个定义 bidding rule 的行为时,只需要对比其参数内是否有包含我们需要的对象,则可以八九不离十的判定出这里面是否有我们需要的 Method 了。同时,如果需要新增 Method,我们也可以参照搜索方法后面 [] 里面提示的路径名,来存放新的 Method。最终开发人员可以直接选择自己需要的方法及需要用到的参数来自动生成测试脚本。
完成 Methods 搜索的关键代码可以参考如下清单,ClassInnerDetail 对象完成最核心的方法、属性的抽取工作,这种抽取适用于任何使用 RFT 的项目,及任何 Java 类型的文件。然后在另一个核心类:TasksForFunction 中,同时遍历配置文件取出的 Function Key Words,以及所有文件对应的方法信息,当二者匹配时,输出对应的方法及参数(此时将参数还原成最小粒度的属性)信息至 Excel 文件中,供用户最后的筛选。这些核心代码文件以及电子表模板都在本文结束后都将上传至共享空间。
public class ClassInnerDetails { public Class getClassObj(String pkgName){}// 构造 Java 对象 public ArrayList<HashMap<String,String>> getMethodInfo(String pkgName) {}// 用反射的方法获得 Java 对象中的方法信息 public ArrayList<HashMap<String,String>> getAttributeInfo(String pkgName) {}// 用反射的方法获得 Java 对象中的属性信息 public void getAttributeMethodInfo_FileStream(String fileName) {}// 用遍历文件的方法获得指定 .java 文件中的方法、属性、子类(方法、属性)信息,并附带其应的包、注释等辅助信息 // 以下三个方法用来记录遍历文件方法的分析结果 public ArrayList<HashMap<String,String>> getAttributesList(){} public ArrayList<HashMap<String,String>> getMethodList(){} public String getImportList(){} }
上一节讨论了如何在既定框架内搜索测试开发人员需要的 Task Layer Methods。当发现并没有合适或相似的方法后,我们通常需要自己新建一个 Method。而这个 Method,往往是一系列 GUI 对象的操作,或者是针对一些 GUI 对象的分析、验证。本节针对这种情况,提供一种自动生成 Task Layer Methods 中 Object Property 文件的方法,使测试开发人员可以专注在 Task Layer Methods 的代码逻辑上,而不需要经常切换 UI 去补充需要的 Object Property 信息,或者添加重复的 Object。下图所示为一般 RFT 测试项目中,Object 层 Object Property 信息的存储方式。RFT 识别到的各个 Object 信息存储于不同的文件中,以方便开发者查找和调用。
每个 RFT 自动化项目都有自己调用对象的方式,使得这些属性需要的封装方式也不尽相同。在笔者所在项目中,提取了一个公共的 Task 类来直接处理所有的对象,例如在 Tasks 类中有 setText(String objProps, String textToSet) 方法,只要传入既定格式的 Object Property 名称,以及需要设置的数据,即可以完成文本域的赋值操作。同理,对按钮、图片、超链接等控件都有相应的方法。这样封装的第一个好处在于,将对象与其操作分离开来,一旦框架完善,测试开发人员只需要关注自己需要添加的 Object 的定位信息即可,不需要再重复将它封装为控件并操作。第二点在于操作是与日志紧密相连的,操作的封装同时避免了每次写日志操作的繁琐。如下为本文使用的 Object Property 封装方式:
public static final String TypeOfBids_Basic="desc=Type of Bids Selection,.class=Html.SELECT,.id=rpt_bid_Status";
本节着重描述生成 Object Property 的过程。Object 通常分为静态对象和动态对象。静态对象是指属性稳定,RFT 每次都可以使用固定属性找到的对象,下表所示为在电子表中的配置域,用来生成静态对象。通过下列域的值,我们可以自动生成其对应的 Object Property 文件。
序号 4~7 的域可以在 GUI 上唯一确定我们需要的 Object,序号 2 和 3 的域赋予这个 Object 在 RFT 项目中的使用代号,供逻辑代码调用。按特定项目的封装要求封装好这个 Object 的 Property 信息后,将其存储中序号 1 的文件中。对于动态对象来说,通常需要根据用户传入的不同参数来决定 Object 的最终 Property 封装内容,这种情况出现的频率并不会太高,可以不必使用自动生成方式实现。
序号 | 配置域 | 说明 | 举例 |
---|---|---|---|
1 | Object Property Repository | Object 需要存储的文件名称 | ObjectRepository_ASM |
2 | Object Name | 调用 Object 的名称 | LoginName |
3 | Desc Name | 在日志中记录的名称 | Login name input textbox |
4 | Object Class | RFT 在 GUI 上识别到的 Object Class 类型 | Html.INPUT.text |
5 | Property | RFT 在 GUI 上识别到的可以定位 Object 的 property 类型 | .title |
6 | Property Value | RFT 在 GUI 上识别到的相应 property 类型的值 | j_username |
7 | Index | 如果需要用 Index 继续定位对象 | 0, 1, 2 or N/A |
下图展示了配置 Object 的电子表,表头为上文提到各个域名。下图蓝色区域用户需要根据 GUI 上的实际情况填写,用来识别对象(来源为 RFT 识别对象的 Object Map)。当用户配置完成后,通过代码将每一行组合为一个新的对象,并插入第一列配置的文件内。最后两列是用来监测所添加的 Object 是否已存在于当前的 Property 文件内,如果存在,则给出存在的文件名,那么用户可以及时做出判断,生成 Object Property 文件时也不再读取该行。此处的代码实现思路为:
由于每个项目的 Object Property 封装方式不尽相同,此处不详细说明其代码实现,其实现思路可以复用。
对于 RFT 自动化测试项目来说,测试数据是独立的一部分,它与测试脚本分离开来有助于自动化项目的维护,通常的储存形式有 XML, Excel 等。本文作者所在的项目使用的是 XML 文件。XML 文件简单且不依赖于平台,但是它的可读性就差于 Excel. 一个 XML 文件里通常包括许多个测试用例的数据 , 测试开发人员通常在写脚本的同时,去填写测试数据,往往需要兼顾脚本文件名、测试数据标签、测试数据等内容,经常因为失误引起语法问题。本文提供 XML 文件的自动生成方式,保证文件格式正确,同时节约一定的时间成本。本节描述两种生成方式绍。
延续上节思路,本文提供通过配置的方法来实现 XML 文件的自动生成。下图中的脚本信息配置区里是测试脚本对应的用例名称等生成 XML 文件需要的信息,可根据自己的 RFT 项目框架要求进行设置。下部的 XML 文件配置区则预置了该项目中常用的一些测试数据类型及 Key 值,使用这样的模板,测试开发人员在用到测试数据时,直接选择(或添加)需要的 Key 值,在 Value 列填入自己需要的数据即可。节省了许多手写 Key-Value 的时间,而且可以使整个项目的风格更加统一。此种方式的代码实现将分享至公共空间。
在上一节中提到可以通过配置功能关键字以及 Tasks 文件路径来搜索相关的方法及其参数,利用这些数据分析结果,开发人员可以直接选择自己需要的方法及了解需要用到的参数生成测试脚本。同时,利用这个分析结果,开发人员可以直接判断需要的测试数据,并提供测试数据。利用这个分析结果生成 XML 也是一个提高开发效率的办法。
首先将上一节产生的所有功能的 Methods 及其方法整合到一表电子表中,以下拉框的形式显示某功能关键字对应的 Method 信息 ( 第二列 )。Method 列表是由所有 Method 名称(及其路径)组成的数据选择列表;根据用户第二列选择的方法,第三列以及其后的列依次显示该方法用到的参数。例如下图中,当用户选择“create lot”功能对应的方法 createItemLot(EventCreationDetails eventCreation) 方法时,其参数是个自定义对象,则其方法体使用到参数 eventCreation 中的具体属性会以 setXX 的形式依次显示。然后,用户可以根据脚本的设计,填写对应的测试数据,< ? > 中的内容为 XML 文件的 key,之后的内容为 value。
此种方法可以让数据与逻辑以更直观的方法对应起来。最后使用代码将所有 XML Data 行有值的部分拼接起来,即可以得到需要的 XML 数据内容。这种方式的应用将在本文下一节实践应用中详细说明其应用场景及其实现。
以下本文通过笔者所做竞拍项目为例来演示从新的回归测试需求确定,到使用测试生成工具快速生成新的测试脚本的过程:
注:测试数据可以以任何方式从 Scripts 中独立出来。我们示例的只是其中的一种 - 用 XML 来做介绍。
本文结合实践经验介绍测试开发人员在对项目整体代码没有完全熟悉的情况下,如何在 RFT 自动化测试项目中利用一些模板、工具实现测试脚本、测试数据、Object Property 的自动生成,并将其插入 RFT 自动化测试框架的正确位置,方便自己熟悉以及给后来者阅读。相信这能够帮助不同项目的 RFT 自动化测试团队成员大大提升熟悉项目的速度,并且提高开发新脚本的效率。使用者需要较为熟悉 RFT 测试项目框架本身的一些特征,所做的事情需要符合、并一步步完善 RFT 项目框架,生成的脚本等其他内容也是为框架本身的完整性服务,因此在笔者提供的代码中,使用者可能需要针对自己项目本身的一些特点,进行定制化修改,例如生成测试脚本的整体格式、Object 的封装方式、测试数据文件的格式等。这种生成方式不可避免也有它的局限性,欢迎感兴趣的朋友与我们共同探讨,继续优化它。