欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用Ruby实现简单的事物驱动的web应用的教程

程序员文章站 2022-11-15 19:47:00
简介 对 web 应用程序来讲,自动化的集成测试是一个非常重要的部分, 然而由于这些测试用例太依赖具体的 web 页面的实现细节,这就给编写和维护带来的很大的挑战。 通常...

简介

对 web 应用程序来讲,自动化的集成测试是一个非常重要的部分, 然而由于这些测试用例太依赖具体的 web 页面的实现细节,这就给编写和维护带来的很大的挑战。 通常来讲有两种方法可以生成 web 应用程序测试用例。

    手工编写脚本:测试人员需要知道 web 页面上有哪些表单、输入框、选择框、按钮等,以及这些表单元素的名称,id 等属性,然后才能利用一些工具来编写测试用例。
    通过工具录制生成:比如 ibm rational functional tester 就提供了录制用户在 web 界面的操作,自动生成测试用例的功能。

方法 1 需要测试人员了解太多的 web 页面细节,这就使得测试人员不能把精力集中在业务逻辑上,一旦 web 页面发生变化,将不得不花费大量精力更新脚本。方法 2 能够自动生成测试脚本,但是这些脚本的可读性很差,导致很难维护。同样如果 web 页面发生变化,测试人员也需要重新录制所有的脚本。

那么有没有办法克服上述问题,让工作更加轻松一点呢?答案是肯定的!

例如一个在线的电子书店,对于用户购书的场景,我们可以用下面的脚本来进行集成测试 :

login 'test@test.com','pass4you'     // 登录
list_books                           // 列出书籍
add_to_shop_cart  '谁说大象不能跳舞'  // 把《谁说大象不能跳舞》这本书加入到购物车中

读者可以看到, "login" , "list_books", "add_to_shop_cart" 这些术语已经完全脱离了具体的页面细节,将不会受到页面变化的影响, 它们是完全面向业务的,准确的体现了应用的业务逻辑,容易理解、易于维护,并且还能拿来和业务人员进行交流,甚至业务人员自己都能编写测试脚本。 有这么多的优点,那么如何实现它们呢?这正是本文要介绍的重点:利用动态语言 ruby 来实现“业务驱动”的 web 应用测试。
ruby 介绍

ruby,中文意思为红宝石,但是在计算机领域,它代表一种相当优秀的面向对象的脚本程序语言。它诞生于 1993 年,近年来随着 ruby on rails 这个“killer application”在 web 开发领域迅速蹿红。ruby 在最初设计时吸收了很多别的语言的精华,例如 perl 语言的文本处理能力,python 语言的简单性和可读性,以及方便的扩展能力和强大的可移植能力,smalltalk 语言的纯面向对象语法思想,这就使它具备了很多其他语言的优点。ruby 的设计理念是尽量减少编程时不必要的琐碎工作,让程序员在完成任务的同时充分的享受编程的乐趣。

ruby 的特点如下:

    面向对象:在 ruby 中,一切皆是对象,包括其他语言中的基本数据类型,比如整数。

    例如在 java 中,对一个数求绝对值用 math.abs(-20), 但在 ruby 中一切皆对象,-20 这个数也是对象,所以可以这么做 -20.abs , 是不是更加形象和直观?
    解释型脚本语言:无需编译,直接执行,开发周期短,调试方便。
    动态性:已经定义的类可以在运行时修改。

本文的重点不是介绍 ruby 语言本身,有兴趣的读者可以参见 参考资源 部分。
案例分析
51book

为了展示如何使用 ruby 进行业务驱动的测试,同时又不让读者陷入到过多细节中,本文假想了一个简单的在线购书应用 ( 简称 51book),这个应用支持如下主要功能:

    1.登录 : 用户必须登录才能购买书籍。
    图 1. 登录

使用Ruby实现简单的事物驱动的web应用的教程

    2.浏览书籍:包括按标题搜索书籍。
图 2. 浏览和搜索书籍

使用Ruby实现简单的事物驱动的web应用的教程

3.把书籍添加到购物车中,参见 图 2 中的“add to cart”链接。
4.改变购物车中书籍的数量,并且重新计算。

使用Ruby实现简单的事物驱动的web应用的教程

业务操作

通过上面的介绍,读者应该对 51book 有了一个简单的了解,接下来我们考虑如何进行业务驱动的测试,首先需要定义面向业务的操作,这样才能在测试用例中使用它们。 简单起见,我们定义如下业务操作:
表 1. 业务操作

使用Ruby实现简单的事物驱动的web应用的教程

领域专用语言 (domain specific language)

所谓领域专用语言(domain specific language / dsl),其基本思想是“求专不求全”,不像通用目的语言那样目标范围涵盖一切软件问题, 而是专门针对某一特定问题的计算机语言。正如它的名称所宣称的那样,这种语言并不是通用的,只是专注于某个特定的“领域”, 例如 sql 语言就是数据库的 dsl,使用 sql 可以完成各种各样数据的操作,而不用关心底层的具体数据库实现。由于“领域专用”,你想用 sql 来开发一个桌面应用程序是不可能的。

我们在上一节定义的 login , add_to_shop_cart , change_quantity 就是针对 51book 在线书店的 dsl。

martin fowler 把 dsl 分为两大类:外部 dsl 和内部 dsl。对外部 dsl 来讲,构建它需要做的是:(1) 定义面向领域的全新的语法。(2) 用某种语言编写解释器或编译器 ,由于这种语言是全新的,我们有很多工作需要做;那么对于内部 dsl 来说,我们可以选定一种灵活的语言,选取它一个语法的子集,并且利用这种语言的动态特性进行定制,这样就避免了重新打造一个全新语言的庞大工作量。

ruby 语言具备非常丰富的语法和异常灵活的动态特征,非常适合创建动态 dsl。本文就是利用 ruby 来创建 51book 面向测试的 dsl。

用 ruby dsl 实现业务操作
原理

由于 ruby 是一种动态脚本语言,是解释执行的,它提供了对一段文本进行 “evaluate”执行的方法。也就是说,我们可以提供一段文本(不必是完整的程序),ruby 就可以在一个特定的上下文中执行它,当然这段文本需要符合 ruby 的语法。

比如我们有一个文件 bookshop.txt,它包含了如下文本 : login "andy", "pass4you" , 那么怎么执行它呢?首先需要一个上下文,我们可以定义一个类来表示:
清单 1. bookshopdslbuilder

class bookshopdslbuilder  
  def self.execute( dsl) 
    builder=new 
    builder.instance_eval(file.read(dsl), dsl)  
  end 
  def login(user=nil,pwd=nil) 
    print user 
    print pwd 
  end 
end

上面的代码非常简单,需要关注的是静态方法 execute, 当把 bookshop.txt 作为参数来调用它时,会有什么情况发生呢 ? 聪明的读者可能已经猜到了,那就是 user 和 pwd 的值会被打印出来。这段代码展示了 ruby 语言的两个重要特点 :

    instance_eval 方法会把一段文本当做代码来执行。执行的上下文就是对象 bookshopdslbuilder。 所以当它碰到文本 "login" 时,会自动调用真正的方法 login。
    在调用一个方法时,可以不加括号。这就是为什么 ruby 会把文本 login "andy","pass4you" 当做一个方法调用的原因。

这两个特点就给我们搭了一座“桥”,使得我们可以把那个面向业务测试的文本诸如“login”,“add_to_cart”,“search_book”等转化为对特定方法的调用了。我们就可以在这些方法中实现某些逻辑。
watir

我们现在已经能够把业务测试的脚本和 ruby 的对象 / 方法连接起来,可是还需要第二座桥把 ruby 和 web 应用程序连接起来,这样才能使业务测试的脚本驱动 web 页面进行测试。我们希望能有一个软件或工具可以像人一样来驱动浏览器的操作,例如点击链接,填充表单,点击按钮等等。当然它也可以检查页面的结果,例如期待的文本是否出现等。

开源工具 watir 就是这样一个工具,除了具备上述功能外,它和 ruby 语言还能进行无缝的集成,并且对浏览器尤其是 ie 有超强的控制能力。所以我们选取它作为第二座桥。

下面是一个使用 watir 的简单例子,它进入 google 的首页,在搜索框中键入 "bookshop", 然后点击"搜索"按钮。 watir 充分继承了 ruby 语言简单明了的特点,读者可以看到使用 watir 的脚本是相当直观,相当容易的。
清单 2. watir 例子

require "watir"
ie = watir::ie.new 
ie.goto "http://www.google.com"    
ie.text_field(:name, "q").set "bookshop"
ie.button(:name, "btng").click

实现 login

有了上面的两座“桥”,具体的实现就简单多了,对于每一个业务操作,我们需要做的是 :

(1) 在一个 ruby 对象中 (bookshopdslbuilder) 实现一个同名的方法

(2) 在方法实现中,利用 watir 来操作界面元素。当然前提是我们需要知道界面上有哪些元素。

先来看一看 login 的实现:
清单 3. login

class bookshopdslbuilder 
 include test::unit::assertions #include ruby unit 的 assertion 
 def self.execute( dsl) 
  builder=new 
  builder.instance_eval(file.read(dsl), dsl) 
  builder 
 end 
 def initialize 
  @login_url = 'http://localhost:3000/bookshop/login'  #51book 的入口
  #creat a ie instance 
  @ie= watir::ie.new               # 创建一个 watir 的实例
 end 
 def login(user=nil,pwd=nil) 
  @ie.goto @login_url 
  @ie.text_field(:id,"user_name").set(user)   # 设置用户名
  @ie.text_field(:id,"user_password").set(pwd)  # 设置密码
  @ie.button(:type,"submit").click        # 点击提交按钮
 end 
end

实现 add_to_shop_cart

把书籍添加的购物车中这个操作相对复杂,因为它接收的参数是一个书籍的标题,而在界面上"add to cart"却是一个只包含 book id, 不包含标题的链接,所以无法直接定位。
清单 4. add to cart

 <table width='100%' class='book'> 
  <tr> 
    <td>title:</td> 
    <td>agile development</td> # 标题在这里
  </tr> 
  <tr> 
    <td>description:</td> 
    <td>the book of agile development</td> 
  </tr> 
  <tr> 
    <td>price:</td> 
    <td>30.0</td> 
  </tr> 
  <tr> 
    <td colspan="2"> #add_to_cart link 却在这里
      <a href='/bookshop/add_to_cart/1' >add to cart</a> 
    </td> 
  </tr>  
 </table>

这种情况下就可以利用 watir 对 xpath 强大的支持,先找到标题,在从标题找到链接,最后点击链接即可。
清单 5. 使用 xpath

def add_to_cart(title)    
  table = @ie.table(:xpath, 
     "//table[@class='book']/tbody/tr/td[text()='"+title+"']/../../../") 
  if table[1][2].text == title 
    href = table[4][1].links[1].href 
    @ie.link(:href,href).click 
  end 
end

对于其他的业务操作,具体的实现方式也是大同小异,这里不再一一介绍,有兴趣的读者可以参见 附件 中的代码,最后我们来看一个面向业务的 web 页面测试例子:
清单 6. 一个完整的例子

 login 'andy','pass4you' 

 add_to_cart 'agile development'
 add_to_cart 'savor blue'
 add_to_cart 'programming ruby' 

 change_quantity 'agile development',10 
 change_quantity 'savor blue',10 
 change_quantity 'programming ruby',10 

 recalculate_cart 
 assert_total_price_is 900 

 search_book 'ant cookbook'
 add_to_cart 'ant cookbook'
 assert_total_price_is 910

总结

到目前为止,我们已经通过 ruby 完整的实现了“业务驱动” 的 web 应用测试,实际上我们通过 ruby 实现了一个面向业务的抽象层,利用 watir 把业务操作映射到了对 html 页面的操作。这样当 html 页面发生了变化的时候,只需要调整映射,而不需要更改业务层的操作。同时由于它们是完全面向业务的,就使得开发人员或测试人员能把精力集中到业务逻辑的测试上,而不用陷入实现的细节。

掌握了该方法以后,读者可以应用到自己的程序中,可以使得自己的测试编写简单,容易理解,易于维护。将会极大的提供 web 应用的测试效率。