簡介
對 Web 應(yīng)用程序來講,自動化的集成測試是一個非常重要的部分, 然而由于這些測試用例太依賴具體的 Web 頁面的實現(xiàn)細節(jié),這就給編寫和維護帶來的很大的挑戰(zhàn)。 通常來講有兩種方法可以生成 Web 應(yīng)用程序測試用例。
手工編寫腳本:測試人員需要知道 Web 頁面上有哪些表單、輸入框、選擇框、按鈕等,以及這些表單元素的名稱,ID 等屬性,然后才能利用一些工具來編寫測試用例。
通過工具錄制生成:比如 IBM Rational Functional Tester 就提供了錄制用戶在 Web 界面的操作,自動生成測試用例的功能。
方法 1 需要測試人員了解太多的 Web 頁面細節(jié),這就使得測試人員不能把精力集中在業(yè)務(wù)邏輯上,一旦 Web 頁面發(fā)生變化,將不得不花費大量精力更新腳本。方法 2 能夠自動生成測試腳本,但是這些腳本的可讀性很差,導(dǎo)致很難維護。同樣如果 Web 頁面發(fā)生變化,測試人員也需要重新錄制所有的腳本。
那么有沒有辦法克服上述問題,讓工作更加輕松一點呢?答案是肯定的!
例如一個在線的電子書店,對于用戶購書的場景,我們可以用下面的腳本來進行集成測試 :
login 'test@test.com','pass4you' // 登錄
list_books // 列出書籍
add_to_shop_cart '誰說大象不能跳舞' // 把《誰說大象不能跳舞》這本書加入到購物車中
讀者可以看到, "login" , "list_books", "add_to_shop_cart" 這些術(shù)語已經(jīng)完全脫離了具體的頁面細節(jié),將不會受到頁面變化的影響, 它們是完全面向業(yè)務(wù)的,準確的體現(xiàn)了應(yīng)用的業(yè)務(wù)邏輯,容易理解、易于維護,并且還能拿來和業(yè)務(wù)人員進行交流,甚至業(yè)務(wù)人員自己都能編寫測試腳本。 有這么多的優(yōu)點,那么如何實現(xiàn)它們呢?這正是本文要介紹的重點:利用動態(tài)語言 Ruby 來實現(xiàn)“業(yè)務(wù)驅(qū)動”的 Web 應(yīng)用測試。
Ruby 介紹
Ruby,中文意思為紅寶石,但是在計算機領(lǐng)域,它代表一種相當優(yōu)秀的面向?qū)ο蟮哪_本程序語言。它誕生于 1993 年,近年來隨著 Ruby on Rails 這個“Killer application”在 Web 開發(fā)領(lǐng)域迅速躥紅。Ruby 在最初設(shè)計時吸收了很多別的語言的精華,例如 perl 語言的文本處理能力,Python 語言的簡單性和可讀性,以及方便的擴展能力和強大的可移植能力,Smalltalk 語言的純面向?qū)ο笳Z法思想,這就使它具備了很多其他語言的優(yōu)點。Ruby 的設(shè)計理念是盡量減少編程時不必要的瑣碎工作,讓程序員在完成任務(wù)的同時充分的享受編程的樂趣。
Ruby 的特點如下:
面向?qū)ο螅涸?Ruby 中,一切皆是對象,包括其他語言中的基本數(shù)據(jù)類型,比如整數(shù)。
例如在 Java 中,對一個數(shù)求絕對值用 Math.abs(-20), 但在 Ruby 中一切皆對象,-20 這個數(shù)也是對象,所以可以這么做 -20.abs , 是不是更加形象和直觀?
解釋型腳本語言:無需編譯,直接執(zhí)行,開發(fā)周期短,調(diào)試方便。
動態(tài)性:已經(jīng)定義的類可以在運行時修改。
本文的重點不是介紹 Ruby 語言本身,有興趣的讀者可以參見 參考資源 部分。
案例分析
51book
為了展示如何使用 Ruby 進行業(yè)務(wù)驅(qū)動的測試,同時又不讓讀者陷入到過多細節(jié)中,本文假想了一個簡單的在線購書應(yīng)用 ( 簡稱 51book),這個應(yīng)用支持如下主要功能:
1.登錄 : 用戶必須登錄才能購買書籍。
圖 1. 登錄
2.瀏覽書籍:包括按標題搜索書籍。
圖 2. 瀏覽和搜索書籍
3.把書籍添加到購物車中,參見 圖 2 中的“Add to cart”鏈接。
4.改變購物車中書籍的數(shù)量,并且重新計算。
業(yè)務(wù)操作
通過上面的介紹,讀者應(yīng)該對 51book 有了一個簡單的了解,接下來我們考慮如何進行業(yè)務(wù)驅(qū)動的測試,首先需要定義面向業(yè)務(wù)的操作,這樣才能在測試用例中使用它們。 簡單起見,我們定義如下業(yè)務(wù)操作:
表 1. 業(yè)務(wù)操作
領(lǐng)域?qū)S谜Z言 (Domain Specific Language)
所謂領(lǐng)域?qū)S谜Z言(domain specific language / DSL),其基本思想是“求專不求全”,不像通用目的語言那樣目標范圍涵蓋一切軟件問題, 而是專門針對某一特定問題的計算機語言。正如它的名稱所宣稱的那樣,這種語言并不是通用的,只是專注于某個特定的“領(lǐng)域”, 例如 SQL 語言就是數(shù)據(jù)庫的 DSL,使用 SQL 可以完成各種各樣數(shù)據(jù)的操作,而不用關(guān)心底層的具體數(shù)據(jù)庫實現(xiàn)。由于“領(lǐng)域?qū)S谩保阆胗?SQL 來開發(fā)一個桌面應(yīng)用程序是不可能的。
我們在上一節(jié)定義的 login , add_to_shop_cart , change_quantity 就是針對 51book 在線書店的 DSL。
Martin Fowler 把 DSL 分為兩大類:外部 DSL 和內(nèi)部 DSL。對外部 DSL 來講,構(gòu)建它需要做的是:(1) 定義面向領(lǐng)域的全新的語法。(2) 用某種語言編寫解釋器或編譯器 ,由于這種語言是全新的,我們有很多工作需要做;那么對于內(nèi)部 DSL 來說,我們可以選定一種靈活的語言,選取它一個語法的子集,并且利用這種語言的動態(tài)特性進行定制,這樣就避免了重新打造一個全新語言的龐大工作量。
Ruby 語言具備非常豐富的語法和異常靈活的動態(tài)特征,非常適合創(chuàng)建動態(tài) DSL。本文就是利用 Ruby 來創(chuàng)建 51book 面向測試的 DSL。
用 Ruby DSL 實現(xiàn)業(yè)務(wù)操作
原理
由于 Ruby 是一種動態(tài)腳本語言,是解釋執(zhí)行的,它提供了對一段文本進行 “evaluate”執(zhí)行的方法。也就是說,我們可以提供一段文本(不必是完整的程序),Ruby 就可以在一個特定的上下文中執(zhí)行它,當然這段文本需要符合 Ruby 的語法。
比如我們有一個文件 bookshop.txt,它包含了如下文本 : login "andy", "pass4you" , 那么怎么執(zhí)行它呢?首先需要一個上下文,我們可以定義一個類來表示:
清單 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
上面的代碼非常簡單,需要關(guān)注的是靜態(tài)方法 execute, 當把 bookshop.txt 作為參數(shù)來調(diào)用它時,會有什么情況發(fā)生呢 ? 聰明的讀者可能已經(jīng)猜到了,那就是 user 和 pwd 的值會被打印出來。這段代碼展示了 Ruby 語言的兩個重要特點 :
instance_eval 方法會把一段文本當做代碼來執(zhí)行。執(zhí)行的上下文就是對象 BookshopDSLBuilder。 所以當它碰到文本 "login" 時,會自動調(diào)用真正的方法 login。
在調(diào)用一個方法時,可以不加括號。這就是為什么 Ruby 會把文本 login "andy","pass4you" 當做一個方法調(diào)用的原因。
這兩個特點就給我們搭了一座“橋”,使得我們可以把那個面向業(yè)務(wù)測試的文本諸如“l(fā)ogin”,“add_to_cart”,“search_book”等轉(zhuǎn)化為對特定方法的調(diào)用了。我們就可以在這些方法中實現(xiàn)某些邏輯。
Watir
我們現(xiàn)在已經(jīng)能夠把業(yè)務(wù)測試的腳本和 Ruby 的對象 / 方法連接起來,可是還需要第二座橋把 Ruby 和 Web 應(yīng)用程序連接起來,這樣才能使業(yè)務(wù)測試的腳本驅(qū)動 Web 頁面進行測試。我們希望能有一個軟件或工具可以像人一樣來驅(qū)動瀏覽器的操作,例如點擊鏈接,填充表單,點擊按鈕等等。當然它也可以檢查頁面的結(jié)果,例如期待的文本是否出現(xiàn)等。
開源工具 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
實現(xiàn) Login
有了上面的兩座“橋”,具體的實現(xiàn)就簡單多了,對于每一個業(yè)務(wù)操作,我們需要做的是 :
(1) 在一個 Ruby 對象中 (BookshopDSLBuilder) 實現(xiàn)一個同名的方法
(2) 在方法實現(xiàn)中,利用 watir 來操作界面元素。當然前提是我們需要知道界面上有哪些元素。
先來看一看 Login 的實現(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)建一個 Watir 的實例
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 # 點擊提交按鈕
end
end
實現(xiàn) add_to_shop_cart
把書籍添加的購物車中這個操作相對復(fù)雜,因為它接收的參數(shù)是一個書籍的標題,而在界面上"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,
"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
對于其他的業(yè)務(wù)操作,具體的實現(xiàn)方式也是大同小異,這里不再一一介紹,有興趣的讀者可以參見 附件 中的代碼,最后我們來看一個面向業(yè)務(wù)的 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
總結(jié)
到目前為止,我們已經(jīng)通過 Ruby 完整的實現(xiàn)了“業(yè)務(wù)驅(qū)動” 的 Web 應(yīng)用測試,實際上我們通過 Ruby 實現(xiàn)了一個面向業(yè)務(wù)的抽象層,利用 Watir 把業(yè)務(wù)操作映射到了對 Html 頁面的操作。這樣當 Html 頁面發(fā)生了變化的時候,只需要調(diào)整映射,而不需要更改業(yè)務(wù)層的操作。同時由于它們是完全面向業(yè)務(wù)的,就使得開發(fā)人員或測試人員能把精力集中到業(yè)務(wù)邏輯的測試上,而不用陷入實現(xiàn)的細節(jié)。
掌握了該方法以后,讀者可以應(yīng)用到自己的程序中,可以使得自己的測試編寫簡單,容易理解,易于維護。將會極大的提供 Web 應(yīng)用的測試效率。
您可能感興趣的文章:- 幾個加速Ruby on Rails的編程技巧
- 利用RJB在Ruby on Rails中使用Java代碼的教程
- Ruby中數(shù)組的一些相關(guān)使用方法