使用Ruby实现简单的事物驱动的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. 登录

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

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

业务操作

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

领域专用语言 (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 应用的测试效率。

(0)

相关推荐

  • 几个加速Ruby on Rails的编程技巧

    Ruby 语言常以其灵活性为人所称道.正如 Dick Sites 所言,您可以 "为了编程而编程".Ruby on Rails 扩展了核心 Ruby 语言,但正是 Ruby 本身使得这种扩展成为了可能.Ruby on Rails 使用了该语言的灵活性,这样一来,无需太多样板或额外的代码就可以轻松编写高度结构化的程序:无需额外工作,就可以获得大量标准的行为.虽然这种轻松自由的行为并不总是完美的,但毕竟您可以无需太多工作就可以获得很多好的架构. 例如,Ruby on Rails 基于模型-

  • 利用RJB在Ruby on Rails中使用Java代码的教程

    开始之前 关于本教程 Ruby on Rails (Rails) 是用 Ruby 编写的一个 full-stack Web 应用程序框架,而 Ruby 是一种功能丰富的.免费的.可扩展的.可移植的.面向对象的脚本编制语言.Rails 在 Web 应用程序开发人员之间非常流行.通过它,可以快速有效地开发 Web 应用程序,并将其部署到任何 Web 容器中,例如 IBM? WebSphere? 或 Apache Tomcat. 在 Rails 和类似的 Web 应用程序开发框架出现之前,用于 Web

  • Ruby中数组的一些相关使用方法

    Ruby数组是有序的,任何对象的??整数索引的集合.每个数组中的元素相关联,并提到的一个索引. 数组下标从0开始,如C或Java.负数索引假设数组末尾---也就是说,-1表示最后一个元素的数组索引,-2是数组中最后一个元素的下一个元素等等. Ruby的数组可以容纳对象,如字符串,整数,长整数,哈希,符号,甚至其他Array对象.Ruby数组没有在其他语言中数组一样严格.Ruby数组自动增长同时增加元素. 创建数组: 有许多方法来创建或初始化一个数组.一种方式是 new 类方法: names =

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

    简介 对 Web 应用程序来讲,自动化的集成测试是一个非常重要的部分, 然而由于这些测试用例太依赖具体的 Web 页面的实现细节,这就给编写和维护带来的很大的挑战. 通常来讲有两种方法可以生成 Web 应用程序测试用例. 手工编写脚本:测试人员需要知道 Web 页面上有哪些表单.输入框.选择框.按钮等,以及这些表单元素的名称,ID 等属性,然后才能利用一些工具来编写测试用例.     通过工具录制生成:比如 IBM Rational Functional Tester 就提供了录制用户在 Web

  • 使用Python简单的实现树莓派的WEB控制

    先给大家展示下效果如图,感觉还很满意请继续阅读全文: 用到的知识:Python Bottle HTML Javascript JQuery Bootstrap AJAX 当然还有 linux 我去,这么多--我还是一点一点说起吧-- 先贴最终的源代码: #!/usr/bin/env python3 from bottle import get,post,run,request,template @get("/") def index(): return template("i

  • 用Eclipse 创建一个简单的web项目(图文教程)

    Eclipse neon 汉化版 ; 1.右击新建 --> 选择 动态Web项目 2. 填写 项目名 项目位置 ; 选择 Dynamic web module version 和 tomcat version ; 点击完成 即可创建 项目; 2.1:项目名称: 2.2:项目位置: 2.3: Dynamic Web Module Version 和 Tomacat Version 之间有版本上的匹配关系: 匹配关系如下图 3. 创建成功后的项目结构: 4. 在创建好项目结构之后 先查看一下 项目的

  • 用python简单实现mysql数据同步到ElasticSearch的教程

    之前博客有用logstash-input-jdbc同步mysql数据到ElasticSearch,但是由于同步时间最少是一分钟一次,无法满足线上业务,所以只能自己实现一个,但是时间比较紧,所以简单实现一个 思路: 网上有很多思路用什么mysql的binlog功能什么的,但是我对mysql了解实在有限,所以用一个很呆板的办法查询mysql得到数据,再插入es,因为数据量不大,而且10秒间隔同步一次,效率还可以,为了避免服务器之间的时间差和mysql更新和查询产生的时间差,所以在查询更新时间条件时是

  • ruby 一些简单的例子

    现在我们将前面的一些示例程序的代码坼开来分析一下. 下面的例子出现在简单的例子一节. def fact(n)       if n == 0            1       else            n * fact(n-1)       end end print fact(ARGV[0].to_i), "\n" 因为是第一次解释,我们将逐行分析. def fact(n) 第一行,def 用于定义一个函数(或者,更准确地说,一个方法(method);我们会在稍后的一节中详

  • Ruby最简单的消息服务器代码

    ser.rb 复制代码 代码如下: require 'socket's = TCPServer.new 3333conn = s.acceptloop do    puts conn.getsend clt.rb 复制代码 代码如下: require 'socket's = TCPSocket.new 'localhost',3333loop do    sms=gets.chomp    s.puts smsend 作者:Hevienz

  • 使用Ruby on Rails快速开发web应用的教程实例

    Ruby on Rails 正在令整个 Web 开发领域受到震憾.让我们首先了解底层的技术: Ruby 是一门免费的.简单的.直观的.可扩展的.可移植的.解释的脚本语言,用于快速而简单的面向对象编程.类似于 Perl,它支持 处理文本文件和执行系统管理任务的很多特性.     Rails 是用 Ruby 编写的一款完整的.开放源代码的 Web 框架,目的是使用更简单而且更少的代码编写实际使用的应用程序. 作为一个完整的框架,这意味着 Rails 中的所有的层都是为协同工作而构造的,所以您不必自己

  • Ruby和Ruby on Rails中解析JSON格式数据的实例教程

    Ruby解析JSON Ruby解析Json例子: json = '["a", "B", "C"]' puts "Unsafe #{unsafe_json (json).inspect}" #输出Unsafe ["a", "B", "C"] Ruby解析Json把上面的json字符串解析成Array.这样的方法并不安全,比如: json = 'puts "Da

  • 简单介绍Python的轻便web框架Bottle

    基本映射 映射使用在根据不同URLs请求来产生相对应的返回内容.Bottle使用route() 修饰器来实现映射. from bottle import route, run@route('/hello')def hello(): return "Hello World!"run() # This starts the HTTP server 运行这个程序,访问http://localhost:8080/hello将会在浏览器里看到 "Hello World!".

  • 简单使用BackgroundWorker创建多个线程的教程

    BackgroundWorker是一个非常不错的线程控件,能避免界面假死,让线程操作你想要做的事,它学习起来很简单,但是能实现很强大的功能.发布这篇文章的目的是将最近学习到的共享出来,大家交流一下,当然我也是菜鸟,在这里你将学习到BackgroundWorker简单使用,停止,暂停,继续等操作,BackgroundWorker比起Thread和ThreadPool要简单太多,为了更方便在实际应用中使用,我使用的是winform,没有使用控制台程序. 在UI界面里拖动一个button和richTe

随机推荐