使用Ruby實(shí)現(xiàn)簡(jiǎn)單的事物驅(qū)動(dòng)的web應(yīng)用的教程
簡(jiǎn)介
對(duì) Web 應(yīng)用程序來(lái)講,自動(dòng)化的集成測(cè)試是一個(gè)非常重要的部分, 然而由于這些測(cè)試用例太依賴具體的 Web 頁(yè)面的實(shí)現(xiàn)細(xì)節(jié),這就給編寫和維護(hù)帶來(lái)的很大的挑戰(zhàn)。 通常來(lái)講有兩種方法可以生成 Web 應(yīng)用程序測(cè)試用例。
手工編寫腳本:測(cè)試人員需要知道 Web 頁(yè)面上有哪些表單、輸入框、選擇框、按鈕等,以及這些表單元素的名稱,ID 等屬性,然后才能利用一些工具來(lái)編寫測(cè)試用例。
通過(guò)工具錄制生成:比如 IBM Rational Functional Tester 就提供了錄制用戶在 Web 界面的操作,自動(dòng)生成測(cè)試用例的功能。
方法 1 需要測(cè)試人員了解太多的 Web 頁(yè)面細(xì)節(jié),這就使得測(cè)試人員不能把精力集中在業(yè)務(wù)邏輯上,一旦 Web 頁(yè)面發(fā)生變化,將不得不花費(fèi)大量精力更新腳本。方法 2 能夠自動(dòng)生成測(cè)試腳本,但是這些腳本的可讀性很差,導(dǎo)致很難維護(hù)。同樣如果 Web 頁(yè)面發(fā)生變化,測(cè)試人員也需要重新錄制所有的腳本。
那么有沒(méi)有辦法克服上述問(wèn)題,讓工作更加輕松一點(diǎn)呢?答案是肯定的!
例如一個(gè)在線的電子書(shū)店,對(duì)于用戶購(gòu)書(shū)的場(chǎng)景,我們可以用下面的腳本來(lái)進(jìn)行集成測(cè)試 :
login 'test@test.com','pass4you' // 登錄
list_books // 列出書(shū)籍
add_to_shop_cart '誰(shuí)說(shuō)大象不能跳舞' // 把《誰(shuí)說(shuō)大象不能跳舞》這本書(shū)加入到購(gòu)物車中
讀者可以看到, "login" , "list_books", "add_to_shop_cart" 這些術(shù)語(yǔ)已經(jīng)完全脫離了具體的頁(yè)面細(xì)節(jié),將不會(huì)受到頁(yè)面變化的影響, 它們是完全面向業(yè)務(wù)的,準(zhǔn)確的體現(xiàn)了應(yīng)用的業(yè)務(wù)邏輯,容易理解、易于維護(hù),并且還能拿來(lái)和業(yè)務(wù)人員進(jìn)行交流,甚至業(yè)務(wù)人員自己都能編寫測(cè)試腳本。 有這么多的優(yōu)點(diǎn),那么如何實(shí)現(xiàn)它們呢?這正是本文要介紹的重點(diǎn):利用動(dòng)態(tài)語(yǔ)言 Ruby 來(lái)實(shí)現(xiàn)“業(yè)務(wù)驅(qū)動(dòng)”的 Web 應(yīng)用測(cè)試。
Ruby 介紹
Ruby,中文意思為紅寶石,但是在計(jì)算機(jī)領(lǐng)域,它代表一種相當(dāng)優(yōu)秀的面向?qū)ο蟮哪_本程序語(yǔ)言。它誕生于 1993 年,近年來(lái)隨著 Ruby on Rails 這個(gè)“Killer application”在 Web 開(kāi)發(fā)領(lǐng)域迅速躥紅。Ruby 在最初設(shè)計(jì)時(shí)吸收了很多別的語(yǔ)言的精華,例如 perl 語(yǔ)言的文本處理能力,Python 語(yǔ)言的簡(jiǎn)單性和可讀性,以及方便的擴(kuò)展能力和強(qiáng)大的可移植能力,Smalltalk 語(yǔ)言的純面向?qū)ο笳Z(yǔ)法思想,這就使它具備了很多其他語(yǔ)言的優(yōu)點(diǎn)。Ruby 的設(shè)計(jì)理念是盡量減少編程時(shí)不必要的瑣碎工作,讓程序員在完成任務(wù)的同時(shí)充分的享受編程的樂(lè)趣。
Ruby 的特點(diǎn)如下:
面向?qū)ο螅涸?Ruby 中,一切皆是對(duì)象,包括其他語(yǔ)言中的基本數(shù)據(jù)類型,比如整數(shù)。
例如在 Java 中,對(duì)一個(gè)數(shù)求絕對(duì)值用 Math.abs(-20), 但在 Ruby 中一切皆對(duì)象,-20 這個(gè)數(shù)也是對(duì)象,所以可以這么做 -20.abs , 是不是更加形象和直觀?
解釋型腳本語(yǔ)言:無(wú)需編譯,直接執(zhí)行,開(kāi)發(fā)周期短,調(diào)試方便。
動(dòng)態(tài)性:已經(jīng)定義的類可以在運(yùn)行時(shí)修改。
本文的重點(diǎn)不是介紹 Ruby 語(yǔ)言本身,有興趣的讀者可以參見(jiàn) 參考資源 部分。
案例分析
51book
為了展示如何使用 Ruby 進(jìn)行業(yè)務(wù)驅(qū)動(dòng)的測(cè)試,同時(shí)又不讓讀者陷入到過(guò)多細(xì)節(jié)中,本文假想了一個(gè)簡(jiǎn)單的在線購(gòu)書(shū)應(yīng)用 ( 簡(jiǎn)稱 51book),這個(gè)應(yīng)用支持如下主要功能:
1.登錄 : 用戶必須登錄才能購(gòu)買書(shū)籍。
圖 1. 登錄

2.瀏覽書(shū)籍:包括按標(biāo)題搜索書(shū)籍。
圖 2. 瀏覽和搜索書(shū)籍

3.把書(shū)籍添加到購(gòu)物車中,參見(jiàn) 圖 2 中的“Add to cart”鏈接。
4.改變購(gòu)物車中書(shū)籍的數(shù)量,并且重新計(jì)算。

業(yè)務(wù)操作
通過(guò)上面的介紹,讀者應(yīng)該對(duì) 51book 有了一個(gè)簡(jiǎn)單的了解,接下來(lái)我們考慮如何進(jìn)行業(yè)務(wù)驅(qū)動(dòng)的測(cè)試,首先需要定義面向業(yè)務(wù)的操作,這樣才能在測(cè)試用例中使用它們。 簡(jiǎn)單起見(jiàn),我們定義如下業(yè)務(wù)操作:
表 1. 業(yè)務(wù)操作

領(lǐng)域?qū)S谜Z(yǔ)言 (Domain Specific Language)
所謂領(lǐng)域?qū)S谜Z(yǔ)言(domain specific language / DSL),其基本思想是“求專不求全”,不像通用目的語(yǔ)言那樣目標(biāo)范圍涵蓋一切軟件問(wèn)題, 而是專門針對(duì)某一特定問(wèn)題的計(jì)算機(jī)語(yǔ)言。正如它的名稱所宣稱的那樣,這種語(yǔ)言并不是通用的,只是專注于某個(gè)特定的“領(lǐng)域”, 例如 SQL 語(yǔ)言就是數(shù)據(jù)庫(kù)的 DSL,使用 SQL 可以完成各種各樣數(shù)據(jù)的操作,而不用關(guān)心底層的具體數(shù)據(jù)庫(kù)實(shí)現(xiàn)。由于“領(lǐng)域?qū)S谩?,你想?SQL 來(lái)開(kāi)發(fā)一個(gè)桌面應(yīng)用程序是不可能的。
我們?cè)谏弦还?jié)定義的 login , add_to_shop_cart , change_quantity 就是針對(duì) 51book 在線書(shū)店的 DSL。
Martin Fowler 把 DSL 分為兩大類:外部 DSL 和內(nèi)部 DSL。對(duì)外部 DSL 來(lái)講,構(gòu)建它需要做的是:(1) 定義面向領(lǐng)域的全新的語(yǔ)法。(2) 用某種語(yǔ)言編寫解釋器或編譯器 ,由于這種語(yǔ)言是全新的,我們有很多工作需要做;那么對(duì)于內(nèi)部 DSL 來(lái)說(shuō),我們可以選定一種靈活的語(yǔ)言,選取它一個(gè)語(yǔ)法的子集,并且利用這種語(yǔ)言的動(dòng)態(tài)特性進(jìn)行定制,這樣就避免了重新打造一個(gè)全新語(yǔ)言的龐大工作量。
Ruby 語(yǔ)言具備非常豐富的語(yǔ)法和異常靈活的動(dòng)態(tài)特征,非常適合創(chuàng)建動(dòng)態(tài) DSL。本文就是利用 Ruby 來(lái)創(chuàng)建 51book 面向測(cè)試的 DSL。
用 Ruby DSL 實(shí)現(xiàn)業(yè)務(wù)操作
原理
由于 Ruby 是一種動(dòng)態(tài)腳本語(yǔ)言,是解釋執(zhí)行的,它提供了對(duì)一段文本進(jìn)行 “evaluate”執(zhí)行的方法。也就是說(shuō),我們可以提供一段文本(不必是完整的程序),Ruby 就可以在一個(gè)特定的上下文中執(zhí)行它,當(dāng)然這段文本需要符合 Ruby 的語(yǔ)法。
比如我們有一個(gè)文件 bookshop.txt,它包含了如下文本 : login "andy", "pass4you" , 那么怎么執(zhí)行它呢?首先需要一個(gè)上下文,我們可以定義一個(gè)類來(lái)表示:
清單 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
上面的代碼非常簡(jiǎn)單,需要關(guān)注的是靜態(tài)方法 execute, 當(dāng)把 bookshop.txt 作為參數(shù)來(lái)調(diào)用它時(shí),會(huì)有什么情況發(fā)生呢 ? 聰明的讀者可能已經(jīng)猜到了,那就是 user 和 pwd 的值會(huì)被打印出來(lái)。這段代碼展示了 Ruby 語(yǔ)言的兩個(gè)重要特點(diǎn) :
instance_eval 方法會(huì)把一段文本當(dāng)做代碼來(lái)執(zhí)行。執(zhí)行的上下文就是對(duì)象 BookshopDSLBuilder。 所以當(dāng)它碰到文本 "login" 時(shí),會(huì)自動(dòng)調(diào)用真正的方法 login。
在調(diào)用一個(gè)方法時(shí),可以不加括號(hào)。這就是為什么 Ruby 會(huì)把文本 login "andy","pass4you" 當(dāng)做一個(gè)方法調(diào)用的原因。
這兩個(gè)特點(diǎn)就給我們搭了一座“橋”,使得我們可以把那個(gè)面向業(yè)務(wù)測(cè)試的文本諸如“l(fā)ogin”,“add_to_cart”,“search_book”等轉(zhuǎn)化為對(duì)特定方法的調(diào)用了。我們就可以在這些方法中實(shí)現(xiàn)某些邏輯。
Watir
我們現(xiàn)在已經(jīng)能夠把業(yè)務(wù)測(cè)試的腳本和 Ruby 的對(duì)象 / 方法連接起來(lái),可是還需要第二座橋把 Ruby 和 Web 應(yīng)用程序連接起來(lái),這樣才能使業(yè)務(wù)測(cè)試的腳本驅(qū)動(dòng) Web 頁(yè)面進(jìn)行測(cè)試。我們希望能有一個(gè)軟件或工具可以像人一樣來(lái)驅(qū)動(dòng)瀏覽器的操作,例如點(diǎn)擊鏈接,填充表單,點(diǎn)擊按鈕等等。當(dāng)然它也可以檢查頁(yè)面的結(jié)果,例如期待的文本是否出現(xiàn)等。
開(kāi)源工具 Watir 就是這樣一個(gè)工具,除了具備上述功能外,它和 Ruby 語(yǔ)言還能進(jìn)行無(wú)縫的集成,并且對(duì)瀏覽器尤其是 IE 有超強(qiáng)的控制能力。所以我們選取它作為第二座橋。
下面是一個(gè)使用 watir 的簡(jiǎn)單例子,它進(jìn)入 Google 的首頁(yè),在搜索框中鍵入 "bookshop", 然后點(diǎn)擊"搜索"按鈕。 Watir 充分繼承了 Ruby 語(yǔ)言簡(jiǎn)單明了的特點(diǎn),讀者可以看到使用 Watir 的腳本是相當(dāng)直觀,相當(dāng)容易的。
清單 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
實(shí)現(xiàn) Login
有了上面的兩座“橋”,具體的實(shí)現(xiàn)就簡(jiǎn)單多了,對(duì)于每一個(gè)業(yè)務(wù)操作,我們需要做的是 :
(1) 在一個(gè) Ruby 對(duì)象中 (BookshopDSLBuilder) 實(shí)現(xiàn)一個(gè)同名的方法
(2) 在方法實(shí)現(xiàn)中,利用 watir 來(lái)操作界面元素。當(dāng)然前提是我們需要知道界面上有哪些元素。
先來(lái)看一看 Login 的實(shí)現(xiàn):
清單 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 # 創(chuàng)建一個(gè) Watir 的實(shí)例 end def login(user=nil,pwd=nil) @ie.goto @login_url @ie.text_field(:id,"user_name").set(user) # 設(shè)置用戶名 @ie.text_field(:id,"user_password").set(pwd) # 設(shè)置密碼 @ie.button(:type,"submit").click # 點(diǎn)擊提交按鈕 end end
實(shí)現(xiàn) add_to_shop_cart
把書(shū)籍添加的購(gòu)物車中這個(gè)操作相對(duì)復(fù)雜,因?yàn)樗邮盏膮?shù)是一個(gè)書(shū)籍的標(biāo)題,而在界面上"Add to Cart"卻是一個(gè)只包含 book id, 不包含標(biāo)題的鏈接,所以無(wú)法直接定位。
清單 4. Add to Cart
<table width='100%' class='book'>
<tr>
<td>title:</td>
<td>Agile development</td> # 標(biāo)題在這里
</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 對(duì) xpath 強(qiáng)大的支持,先找到標(biāo)題,在從標(biāo)題找到鏈接,最后點(diǎn)擊鏈接即可。
清單 5. 使用 XPath
def add_to_cart(title)
table = @ie.table(:xpath,
"http://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
對(duì)于其他的業(yè)務(wù)操作,具體的實(shí)現(xiàn)方式也是大同小異,這里不再一一介紹,有興趣的讀者可以參見(jiàn) 附件 中的代碼,最后我們來(lái)看一個(gè)面向業(yè)務(wù)的 Web 頁(yè)面測(cè)試?yán)樱?br /> 清單 6. 一個(gè)完整的例子
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
總結(jié)
到目前為止,我們已經(jīng)通過(guò) Ruby 完整的實(shí)現(xiàn)了“業(yè)務(wù)驅(qū)動(dòng)” 的 Web 應(yīng)用測(cè)試,實(shí)際上我們通過(guò) Ruby 實(shí)現(xiàn)了一個(gè)面向業(yè)務(wù)的抽象層,利用 Watir 把業(yè)務(wù)操作映射到了對(duì) Html 頁(yè)面的操作。這樣當(dāng) Html 頁(yè)面發(fā)生了變化的時(shí)候,只需要調(diào)整映射,而不需要更改業(yè)務(wù)層的操作。同時(shí)由于它們是完全面向業(yè)務(wù)的,就使得開(kāi)發(fā)人員或測(cè)試人員能把精力集中到業(yè)務(wù)邏輯的測(cè)試上,而不用陷入實(shí)現(xiàn)的細(xì)節(jié)。
掌握了該方法以后,讀者可以應(yīng)用到自己的程序中,可以使得自己的測(cè)試編寫簡(jiǎn)單,容易理解,易于維護(hù)。將會(huì)極大的提供 Web 應(yīng)用的測(cè)試效率。
相關(guān)文章
使用ruby部署工具mina快速部署nodejs應(yīng)用教程
這篇文章主要介紹了使用ruby部署工具mina快速部署nodejs應(yīng)用教程,包含mina的配置、運(yùn)行以及發(fā)布示例,需要的朋友可以參考下2014-09-09
Java 版的 Ruby 解釋器 JRuby 1.7.14 發(fā)布
JRuby,一個(gè)采用純Java實(shí)現(xiàn)的Ruby解釋器,由JRuby團(tuán)隊(duì)開(kāi)發(fā)。它是一個(gè)自由軟件,在CPL/GPL/LGPL三種許可協(xié)議下發(fā)布。2014-08-08
淺談Ruby on Rails下的rake與數(shù)據(jù)庫(kù)數(shù)據(jù)遷移操作
Rails中的Migration相對(duì)來(lái)說(shuō)更適合做數(shù)據(jù)庫(kù)的對(duì)象集合操作,而自動(dòng)化的rake則是一個(gè)較好的選擇,下面來(lái)淺談Ruby on Rails下的rake與數(shù)據(jù)庫(kù)數(shù)據(jù)遷移操作,需要的朋友可以參考下2016-06-06
Ruby中使用多線程隊(duì)列(Queue)實(shí)現(xiàn)下載博客文章保存到本地文件
這篇文章主要介紹了Ruby中使用多線程隊(duì)列(Queue)實(shí)現(xiàn)下載博客文章保存到本地文件,本文給出了實(shí)現(xiàn)代碼、并對(duì)代碼的核心部分做了講解,同時(shí)給出了運(yùn)行效果圖,需要的朋友可以參考下2015-01-01
ruby中執(zhí)行周期性任務(wù)(定時(shí)任務(wù))的3種方法
這篇文章主要介紹了ruby中執(zhí)行周期性任務(wù)(定時(shí)任務(wù))的3種方法,本文通過(guò)使用whenever、sidetiq、clockwork等gem實(shí)現(xiàn),需要的朋友可以參考下2014-10-10
rails "No route matches" 錯(cuò)誤的解決方法
有時(shí)候 rails 會(huì)出現(xiàn)2008-12-12

