您的位置:軟件測(cè)試 > 開源軟件測(cè)試 > 開源功能測(cè)試工具 > Selenium
應(yīng)用Selenium和Ruby進(jìn)行面向領(lǐng)域的Web測(cè)試
作者:網(wǎng)絡(luò)轉(zhuǎn)載 發(fā)布時(shí)間:[ 2013/2/22 13:38:49 ] 推薦標(biāo)簽:

應(yīng)用Selenium進(jìn)行Web測(cè)試時(shí),經(jīng)常會(huì)遇到下面的幾個(gè)麻煩問題:

大量使用name、id、xpath等頁面元素。無論是功能修改、UI重構(gòu)還是交互性改進(jìn)都會(huì)影響到這些元素,這使得Selenium測(cè)試變得非常脆弱。
過于細(xì)節(jié)的頁面操作不容易體現(xiàn)出行為的意圖,一段時(shí)間之后很難真正把握測(cè)試原有的目的了,這使得Selenium測(cè)試變得難于維護(hù)。 對(duì)具體數(shù)據(jù)取值的存在依賴,當(dāng)個(gè)別數(shù)據(jù)不再合法的時(shí)候,測(cè)試會(huì)失敗,但這樣的失敗并不能標(biāo)識(shí)功能的缺失,這使得Selenium測(cè)試變得脆弱且難以維護(hù)。

而這幾點(diǎn)直接衍生的結(jié)果是不斷地添加新的測(cè)試,而極少地去重構(gòu)、利用原有測(cè)試。其實(shí)這倒也是正常,單元測(cè)試測(cè)試寫多了,也有會(huì)有這樣的問題。不過比較要命的是,Selenium的執(zhí)行速度比較慢(相對(duì)單元測(cè)試),隨著測(cè)試逐漸的增多,運(yùn)行時(shí)間會(huì)逐漸增加到不可忍受的程度。一組意圖不明而且難以維護(hù)的Selenium測(cè)試,可以很輕松地在每次構(gòu)建(Build)的時(shí)候殺掉40分鐘甚至2個(gè)小時(shí)的時(shí)間,我有曾有花2個(gè)小時(shí)坐在電腦前面等待450個(gè)Selenium測(cè)試運(yùn)行通過的悲慘經(jīng)歷。因此合理有效地規(guī)劃Selenium測(cè)試顯得格外的迫切和重要了。而目前比較行之有效的辦法,往大了說,可以叫基于領(lǐng)域的Web測(cè)試(Domain Based Web Testing),具體來講,是Page Object Pattern。

Page Object Pattern里有四個(gè)基本概念:Driver、Page、Navigator和Shortcut等。Driver是測(cè)試真正的實(shí)現(xiàn)機(jī)制,比如Selenium,比如Watir,比如HttpUnit。它們懂得如何去真正執(zhí)行一個(gè)Web行為,通常包含像Click、Select、Type等這樣的表示具體行為的方法;Page是對(duì)一個(gè)具體頁面的封裝,它們了解頁面的結(jié)構(gòu),知道諸如id、name、class和xpath這類實(shí)現(xiàn)細(xì)節(jié),并描述用戶可以在其上進(jìn)行何種操作;Navigator則代表了URL,表示一些不經(jīng)頁面操作的直接跳轉(zhuǎn);后Shortcut是helper方法了,需要看具體的需要而定。下面來看一個(gè)超級(jí)簡(jiǎn)單的例子——測(cè)試登錄頁面。

1. Page Object

假設(shè)我們使用一個(gè)單獨(dú)的登錄頁面進(jìn)行登錄,那么可能會(huì)將登錄的操作封裝在一個(gè)名為L(zhǎng)oginPage的page object里:
class LoginPage def initialize driver @driver = driver end def login_as user @driver.type 'id=...', user[:name] @driver.type 'xpath=...', user[:password] @driver.click 'name=...' @driver.wait_for_page_to_load end end

login_as是一個(gè)具有業(yè)務(wù)含義的頁面行為。在login_as方法中,page object負(fù)責(zé)通過依靠id、xpath、name等信息完成登錄操作。在測(cè)試中,我們可以這樣來使用這個(gè)page object:
page = LoginPage.new $selenium page.login_as :name => 'xxx', :password => 'xxx'

不過既然用了Ruby,總要用一些ruby sugar吧,我們定義一個(gè)on方法來表達(dá)頁面操作的環(huán)境:

def on page_type, &block page = page_type.new $selenium page.instance_eval &block if block_given? end

之后我們可以使用page object的類名常量和block描述在某個(gè)特定頁面上操作了:
on LoginPage do login_as :name => 'xxx', :password => 'xxx' end

除了行為方法之外,我們還需要在page object上定義一些獲取頁面信息的方法,比如獲取登錄頁面的歡迎詞的方法:

def welcome_message @driver.get_text 'xpath=...' end

這樣測(cè)試也可表達(dá)得更生動(dòng)一些:
on LoginPage do assert_equal 'Welcome!', welcome_message login_as :name => 'xxx', :password => 'xxx' end

當(dāng)你把所有的頁面都用Page Object封裝了之后,有效地分離了測(cè)試和頁面結(jié)構(gòu)的耦合。在測(cè)試中,只需使用諸如login_as和add_product_to_cart這樣的業(yè)務(wù)行為,而不必依靠像id、name等這些具體且易變的頁面元素了。當(dāng)這些頁面元素發(fā)生變化時(shí),只需修改相應(yīng)的page object可以了,而原有測(cè)試基本不需要太大或太多的改動(dòng)。
2. Assertation

只有行為還構(gòu)不成測(cè)試,我們還要判斷行為結(jié)果,并進(jìn)行一些斷言。簡(jiǎn)單回顧一下上面的例子,會(huì)發(fā)現(xiàn)還有一些很重要的問題沒有解決:我怎么判斷登錄成功了呢?我如何才能知道真的是處在登錄頁面了呢?如果我調(diào)用下面的代碼會(huì)怎樣呢?

$selenium.open url_of_any_page_but_not_login on LoginPage {...}

因此我們還需要向page object增加一些斷言性方法。至少,每個(gè)頁面都應(yīng)該有一個(gè)方法用于判斷是否真正地達(dá)到了這個(gè)頁面,如果不處在這個(gè)頁面中的話,不能進(jìn)行任何的業(yè)務(wù)行為。下面修改LoginPage使之包含這樣一個(gè)方法:
LoginPage.class_eval do include Test::Unit::Asseration def visible? @driver.is_text_present(...) && @driver.get_location == ... end end

在visible?方法中,我們通過對(duì)一些特定的頁面元素(比如URL地址,特定的UI結(jié)構(gòu)或元素)進(jìn)行判斷,從而可以得之是否真正地處在某個(gè)頁面上。而我們目前表達(dá)測(cè)試的基本結(jié)構(gòu)是由on方法來完成,我們也順理成章地在on方法中增加一個(gè)斷言,來判斷是否真的處在某個(gè)頁面上,如果不處在這個(gè)頁面則不進(jìn)行任何的業(yè)務(wù)操作:
def on page_type, &block page = page_type.new $selenium assert page.visible?, "not on #{page_type}" page.instance_eval &block if block_given? page end

這個(gè)方法神秘地返回了page對(duì)象,這里是一個(gè)比較取巧的技巧。實(shí)際上,我們只想利用page != nil這個(gè)事實(shí)來斷言頁面的流轉(zhuǎn),比如,下面的代碼描述登錄成功的頁面流轉(zhuǎn)過程:

on LoginPage do assert_equal 'Welcome!', welcome_message login_as :name => 'xxx', :password => 'xxx' end assert on WelcomeRegisteredUserPage

除了這個(gè)基本斷言之外,我們還可以定義一些業(yè)務(wù)相關(guān)的斷言,比如在購物車頁面里,我們可以定義一個(gè)判斷購物車是否為空的斷言:

def cart_empty? @driver.get_text('xpath=...') == 'Shopping Cart(0)' end

需要注意的是,雖然我們?cè)趐age object里引入了Test::Unit::Asseration模塊,但是并沒有在斷言方法里使用任何assert*方法。這是因?yàn)椋拍钌蟻碇vpage object并不是測(cè)試。使之包含一些真正的斷言,一則概念混亂,二則容易使page object變成針對(duì)某些場(chǎng)景的test helper,不利于以后測(cè)試的維護(hù),因此我們往往傾向于將斷言方法實(shí)現(xiàn)為一個(gè)普通的返回值為boolean的方法。

軟件測(cè)試工具 | 聯(lián)系我們 | 投訴建議 | 誠聘英才 | 申請(qǐng)使用列表 | 網(wǎng)站地圖
滬ICP備07036474 2003-2017 版權(quán)所有 上海澤眾軟件科技有限公司 Shanghai ZeZhong Software Co.,Ltd