写点什么

使用 iTest2 重构自动化功能测试脚本

  • 2009-08-31
  • 本文字数:6008 字

    阅读完需:约 20 分钟

介绍

众所周知,自动测试脚本很难维护。随着敏捷方法学在企业软件项目中的广泛应用,其核心实践之一——自动化功能测试已经证明了它的价值,同时却也对项目提出了挑战。传统的“录制-回播”类型的测试工具也许能帮助测试人员很快地创建一系列的测试脚本,但这些测试代码最后却很难维护。原因就是:应用程序在不断变化。

在编程的世界中,“重构”(在不影响软件外在行为的前提下,改善软件内部结构的一种方法)已经成为程序员之间频繁使用的词汇。简而言之,通过重构,程序员让代码变得更易于理解、设计也更灵活。经验丰富的敏捷项目经理会给程序员分配一定的时间来重构代码,或者把重构作为完成用户故事的一部分。大部分的集成开发环境(IDE)已经对多种重构方式提供了内置支持。

开发或者维护自动测试脚本的测试人员就没有这份惬意了,虽然他们也有使自动测试脚本变得可读和可维护的要求。软件发布新版本,会伴随新特性、bug 修复和软件变更,要想跟踪与之对应的测试脚本,这很难(而且,测试脚本越多,这项工作就越困难)。

测试重构

对功能测试的重构目标和流程与代码重构一样,但有自己的特点:

  • 目标受众
    测试工具的最终用户包括测试人员、业务分析师,甚至还有客户。事实是测试人员、业务分析师和客户一般都不掌握编程技能,整个范式因此而改变。
  • 脚本语法
    代码重构主要是在编译型语言(比如 Java 和 C#)上得到支持。函数式测试脚本,可能是 XML、厂商专有脚本、编译型语言或者脚本语言(比如 Ruby)。根据测试框架不同,重构的使用形式也不同。
  • 功能测试专属重构
    很多通用的代码重构技巧,比如“重命名”,可以用在功能测试脚本里面,它们特定于测试意图,比如“Move the scripts to run each test case”。

iTest2 IDE

iTest2 IDE 是一款新的功能测试工具,专为测试人员设计,让他们能够很轻松地开发和维护自动测试脚本。iTest2 完全致力于 web 测试的自动化,它支持的测试框架是使用 RSpec 语法的 rWebUnit(是广为流行的 Watir 的一款开源插件)。

iTest2 背后的哲学是:容易、简单。试用显示:没有编程经验的测试人员在指导下,平均只需要少于 10 分钟的时间就能编写他们第一个自动化测试脚本。借助于 iTest2,测试人员可以开发、维护和验证功能需求的测试脚本;开发人员可以验证特性可用;业务分析师 / 客户通过查看测试运行结果(在真实的浏览器下,比如 IE 或者 Firefox)来验证功能需求。

由 iTest2 创建的测试脚本可以从命令行运行,也能集成在持续构建服务器上。

演练

事实胜于雄辩。下面我们就来看看如何使用 iTest2 提供的重构工具创建两个测试用例,使它们变得更易理解和维护。

测试计划

为了练习,我们给 Mecury’s NewTour 网站开发了一些典型但是简单的 web 测试脚本。

站点 URL http://newtours.demoaut.com 测试数据: 用户登录:agileway / agileway 测试用例 001: 一个注册客户可以选择单程航行方式,从纽约前往悉尼。 测试用例 002: 一个注册客户可以选择往返方式,从纽约前往悉尼。 自动化测试 测试脚本框架: rWebUnit(开源的 Watir 扩展) 测试执行方法: 通过命令行或 iTest2 IDE 测试编辑器 / 工具: iTest2 IDE ### 创建测试用例 001

1. 创建项目

首先,我们创建一个 iTest2 项目,指定网站 URL。一个简单的测试脚本文件就会被创建出来,如下所示:

复制代码
load File.dirname(__FILE__) + '/test_helper.rb'
test_suite "TODO" do
include TestHelper
before(:all) do
open_browser "http://newtours.demoaut.com"
end
test "your test case name" do
# add your test scripts here
end
end

2. 使用 iTest2Recorder 录制测试用例 001 的测试脚本

我们使用 iTest2Recorder,这是 Firefox 的一个插件,能录制用户在 Firefox 浏览器中的操作,并记录为可执行的测试脚本。

复制代码
enter_text("userName", "agileway")
enter_text("password", "agileway")
click_button_with_image("btn_signin.gif")
click_radio_option("tripType", "oneway")
select_option("fromPort", "New York")
select_option("toPort", "Sydney")
click_button_with_image("continue.gif")
assert_text_present("New York to Sydney")

3. 把录好的测试脚本贴到一个测试脚本文件里面,运行

复制代码
# ...
test "[001] one way trip" do
enter_text("userName", "agileway")
enter_text("password", "agileway")
click_button_with_image("btn_signin.gif")
click_radio_option("tripType", "oneway")
select_option("fromPort", "New York")
select_option("toPort", "Sydney")
click_button_with_image("continue.gif")
assert_text_present("New York to Sydney")
end

现在运行测试用例(右键单击,然后选择“Run [001] one way trip”),它通过了!

使用 Page 对象进行重构

上面的测试脚本可以工作,而且 rWebUnit 语法也非常易读。有人可能对重构的要求提出质疑,也许还会问“使用 Page”是怎么回事?

首先,以现在的格式来看,测试脚本并不易于维护。假设我们已经有了数百个自动测试脚本,而新发布的软件修改了用户认证方式,使用客户邮箱作为用户名登录,这意味着我们需要在测试脚本里面使用‘email’,而不再是‘userName’。在数百个文件里面查找替换,那可不是个好主意。况且,项目成员也喜欢使用项目里面的通用词汇,有一个很美妙的名字来称呼它们:领域专属语言(DSL)。在测试脚本里面也使用这些词汇就太美妙了。

使用 Page 对象能很好地做到这一点。一个我们所说的 Page 对象代表了一个逻辑上的 web 页面,它包含了最终用户在该页面上可以执行的操作。举例来说,在我们例子里面的主页就包含了三个操作:“输入用户名”、“输入密码”和“点击登录按钮”。“使用 Page 对象进行重构”是指把操作抽取到特定 Page 对象的过程,而 iTest2 提供了对这样的重构支持,你可以很容易做到这一点。

1. 抽取到 HomePage 对象

登录功能是发生在主页上面,我们把这事交给 HomePage。用户登录是一个很常见的功能,我们用了三行语句(输入用户名、输入密码和点击登录按钮)完成这个操作。选中这三行代码,然后在“Refactoring”菜单下单击“Extract Page…”(快捷键是 Ctrl+Alt+G)。

图 1. “Refactor”菜单——“Extract Page”

如下图所示,这样会弹出一个窗口,让你输入 Page 对象的名字和功能名。这里,我们分别输入“HomePage”和“login”。

图 2. “Extract Page”对话框

选中的 3 行代码就被替换成:

复制代码
home_page = expect_page HomePage
home_page.login
{1}

这将会自动创建一个新文件“pages\home_page.rb”,其内容如下:

复制代码
class HomePage < RWebUnit::AbstractWebPage
def initialize(browser)
super(browser, "") # TODO: add identity text (in quotes)
end
def login
enter_text("userName", "agileway")
enter_text("password", "agileway")
click_button_with_image("btn_signin.gif")
end
end

再次运行测试用例,它应该还是可以通过。

注意:正如 Martin Fowler 指出,重构的节奏:测试、小的改动、测试、小的改动。正是这种节奏保证了重构的迅速和安全。

2. 抽取 SelectFlightPage

登录成功之后,顾客进入了航班选择页面。与登录页面不同,这里的每个操作很可能被不同的开发人员修改,所以我们把每个操作都抽取为一个函数。把光标移到这一行

click_radio_option("tripType", "oneway")

再次执行“Extract to Page...”重构命令(Ctrl+Alt+G),给新的 Page 对象和函数名输入“SelectFlightPage”和“select_trip_oneway”。

复制代码
select_flight_page = expect_page SelectFlightPage
select_flight_page.select_trip_oneway
{1}

3. 继续抽取更多的操作到 SelectFlightPage 对象

继续把“SelectFlightPage”上的操作重构成函数:“select_from_new_york”、“select_to_sydney”和“click_continue”。

复制代码
test "[1] one way trip" do
home_page = expect_page HomePage
home_page.login
select_flight_page = expect_page SelectFlightPage
select_flight_page.select_trip_oneway
select_flight_page.select_from_new_york
select_flight_page.select_to_sydney
select_flight_page.click_continue
assert_text_present("New York to Sydney")
end

跟往常一样,我们再一次运行测试用例。

编写测试用例 002

在重构完测试用例 001 之后,我们现在有了 2 个 Page 对象(“HomePage”和“SelectFlightPage”),因此(通过重用它们)编写测试用例 002 会容易很多

1. 使用已有的 HomePage

iTest2 IDE 内置支持 Page 对象,输入“ep”再敲“Tab”制表键(称为“snippets”),就能自动补全为“expect_page”并且弹出所有已知的 Page 对象以供选择。

图 3. 自动补全 Page 对象

我们就能得到

expect_page HomePage

为了使用 HomePage,我们需要持有它的句柄(在编程世界中,也被称为‘变量’)。执行“Introduce Page Variable”重构动作(Ctrl+Alt+V)创建一个新变量。

图 4. ‘Refactor’菜单 - “Introduce Page Variable”菜单项

home_page = expect_page HomePage

现在在新行中输入“home_page.”,会自动提示这个 Page 对象中定义的函数供你选择。

图 5. Page 对象函数查找

2. 添加测试用例 2 需要的方法

测试用例 002 跟测试用例 001 很像,区别只在于旅行类型的选择和断言。借助于 Recorder,我们可以定义出新的函数:

click_radio_option("tripType", "roundtrip")

把它重构成 SelectFlightPage 的一个新功能

select_flight_page.select_trip_round

就变成了

复制代码
test "[2] round trip" do
home_page = expect_page HomePage
home_page.login
select_flight_page = expect_page SelectFlightPage
select_flight_page.select_trip_round
select_flight_page.select_from_new_york
select_flight_page.select_to_sydney
select_flight_page.click_continue
assert_text_present("New York to Sydney")
assert_text_present("Sydney to New York")
end

运行测试用例 2 的测试脚本(在测试用例 2 的任意一行之上单击右键,选择“Run …”),测试也通过了!

把应用复原为原始状态

但是等一等,我们还没有完成。测试用例 1 通过了,测试用例 2 也通过了,但是当把它们一起运行的时候,测试用例 2 却失败了,为什么?

我们没有把 web 应用复原回初始状态,在运行完测试用例 001 之后用户还是保持登录的状态。为了让测试之间互相保持独立,我们要确保每次运行测试都要以登录开始,以退出结束,有始有终。

复制代码
test "[001] one way trip" do
home_page = expect_page HomePage
home_page.login
# . . .
click_link("SIGN-OFF")
goto_page("/")
end
test "[002] round trip" do
home_page = expect_page HomePage
home_page.login
# . . .
click_link("SIGN-OFF")
goto_page("/")
end

删除重复代码

测试脚本存在着明显的重复。RSpec 框架允许用户在每个测试用例运行之前或之后执行某些操作。

选中首部两行(登录功能),按下“Shift + F7”以执行“Move Code”重构。

图 6. 重构菜单“Move code”

选择“2 Move to before(:each)”,把这部分操作移到

复制代码
before(:each) do
home_page = expect_page HomePage
home_page.login
end

正如名字所示,这两步操作会在每个测试用例运行之前执行,所以测试用例 002 里面的前面两行也就没有存在的必要了。我们还可以执行相似的重构,完成“after(:each)”的相关部分。

复制代码
after(:each) do<p>click_link("SIGN-OFF")</p><p>goto_page("/")</p><p>end</p>

最终版本

以下是测试用例 001 和 002 的完整的(经过充分重构的)测试脚本。

复制代码
load File.dirname(__FILE__) + '/test_helper.rb'
test_suite "Complete Test Script" do
include TestHelper
before(:all) do
open_browser "http://newtours.demoaut.com"
end
before(:each) do
home_page = expect_page HomePage
home_page.login
end
after(:each) do
click_link("SIGN-OFF")
goto_page("/")
end
test "[001] one way trip" do
select_flight_page = expect_page SelectFlightPage
select_flight_page.select_trip_oneway
select_flight_page.select_from_new_york
select_flight_page.select_to_sydney
select_flight_page.click_continue
assert_text_present("New York to Sydney")
end
test "[002] round trip" do
select_flight_page = expect_page SelectFlightPage
select_flight_page.select_trip_round
select_flight_page.select_from_new_york
select_flight_page.select_to_sydney
select_flight_page.click_continue
assert_text_present("New York to Sydney")
assert_text_present("Sydney to New York")
end
end

适应变化

我们的世界并不完美。在软件开发行业,事物频繁发生变更。幸运的是,以上的工作使得测试脚本不仅仅更易读,而且也更容易适应变化。

1. 客户修改了术语

众所周知,项目使用同一套语言是一个好的实践,即使在测试脚本里面也是如此。举例来说,客户现在更倾向于使用“Return Trip”这个名词,而不再是“Round Trip”。借助于重构测试脚本,这很容易做到。

把光标移到“SelectFlightPage”类(pages\select_flight_page.rb)的“select_trip_round”函数,在“Refactoring”菜单下选择“Rename …”项(Shift+F6)

图 7. “Refactor”菜单-“Rename”

然后输入新的函数名字“select_return_trip”。

图 8. “Rename Function”对话框

测试脚本其他引用“select_trip_round”的地方就都更改为

select_flight_page.select_return_trip

2. 应用程序的修改

应用程序(来自程序员)的修改就更普遍了。举例来说,程序员基于某些原因修改了航班选择页面,导致 HTML 页面上出发城市的属性从

复制代码
<select name="fromPort">

改成

复制代码
<select name="departurePort">

虽然用户不会察觉到任何变化,测试脚本(任何访问这个页面的测试用例)现在却会失败。如果你直接用录制的脚本文件作为测试脚本,修改的操作将会非常乏味,而且易于引入错误。

定位到“SelectFlightPage”的“select_from_new_york”方法(使用快捷键 Ctrl+T 选中“select_flight_page”,再输入快捷键 Ctrl+F12 选择“select_from_xx”),把“fromPort”改成“departurePort”。

复制代码
def select_from_new_york
select_option("departurePort", "New York") # from 'fromPort'
end

看上去还不赖!

结论

本文我们介绍了在自动化功能测试中使用 Page 对象,以使测试脚本易于理解和维护。通过一个使用 iTest2 IDE 改善测试脚本过程的实际例子,我们演示了其提供的丰富的重构功能。

引用文献

Fowler, Martin, et al. Refactoring: Improving the design of existing code, Reading, Mass.: Addison-Wesley, 1999


感谢郑柯对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2009-08-31 00:563690
用户头像

发布了 76 篇内容, 共 24.3 次阅读, 收获喜欢 3 次。

关注

评论

发布
暂无评论
发现更多内容

区块链技术革命才刚刚开始

CECBC

主客体建模法

少个分号

21 K8S之容器存储接口CSI

穿过生命散发芬芳

k8s 11月日更

考试试卷存储架构

AHUI

「架构实战营」

模块四

侠客行

架构实战营 「架构实战营」

模块四作业

心怀架构

堪称数据库的天花板,极致经典的字节内部MySQL宝典意外流出!

热爱java的分享家

Java 面试 程序人生 编程语言 经验分享

spring的事务隔离级别是何许人也

卢卡多多

Spring Framework 隔离级别 11月日更

Golang Gin 框架上传介绍(四)

liuzhen007

11月日更

公司给我股份,有什么需要注意的吗?

石云升

创业公司 11月日更

个人博客网站升级

北漂码农有话说

网站

和不安全访问say goodbye,手把手教大家如何给域名申请免费SSL证书

老表

https 云服务器 SSL证书 跟老表学云服务器

css布局之全屏布局

Augus

CSS 11月日更

区块链和人工智能是否可以保护森林不再被砍伐?

CECBC

Lua-Table

Changing Lin

11月日更

模块四:如何设计业务高性能高可用存储架构?-- 学习总结

小鹿

Python Qt GUI设计:QPushButton、QRadioButton和QCheckBox按钮类(基础篇—12)

不脱发的程序猿

GUI设计 Python Qt QPushButton QRadioButton QCheckBox

微服务治理之自适应降载

万俊峰Kevin

微服务 微服务架构 服务治理 go-zero Go 语言

[Pulsar] 一个消息的生命历程(一)

Zike Yang

Apache Pulsar 11月日更

模块4作业

忘记喝水的猫

架构训练营

GitLab CI/CD 自动化构建与发布实践

Se7en

Go语言学习查缺补漏ing Day3

Regan Yue

Go 语言 11月日更

千万级考试试卷存储方案

张靖

#架构实战营

用AI走出“沙丘”

白洞计划

hw4-千万级存储方案

WWH

架构实战营

模块四作业 千万级学生管理系统的考试试卷存储方案

小朱

架构实战营

在线等比数列求和计算器

入门小站

工具

Apache Pulsar 与 Apache Kafka 在金融场景下的性能对比分析

Apache Pulsar

大数据 kafka 架构 云原生 Apache Pulsar

元宇宙:区块链时代的新名字

CECBC

flutter中的列表的性能优化前奏

坚果

flutter 11月日更

linux之拖拽上传文件

入门小站

Linux

使用iTest2重构自动化功能测试脚本_研发效能_Zhimin Zhan_InfoQ精选文章