應用Selenium進行Web測試時,經常會遇到下面的幾個麻煩問題:
大量使用name、id、xpath等頁面元素。無論是功能修改、UI重構還是交互性改進都會影響到這些元素,這使得Selenium測試變得非常脆弱。
過于細節的頁面操作不容易體現出行為的意圖,一段時間之后就很難真正把握測試原有的目的了,這使得Selenium測試變得難于維護。
對具體數據取值的存在依賴,當個別數據不再合法的時候,測試就會失敗,但這樣的失敗并不能標識功能的缺失,這使得Selenium測試變得脆弱且難以維護。
而這幾點直接衍生的結果就是不斷地添加新的測試,而極少地去重構、利用原有測試。其實這倒也是正常,單元測試測試寫多了,也有會有這樣的問題。不過比較要命的是,Selenium的執行速度比較慢(相對單元測試),隨著測試逐漸的增多,運行時間會逐漸增加到不可忍受的程度。一組意圖不明而且難以維護的Selenium測試,可以很輕松地在每次構建(Build)的時候殺掉40分鐘甚至2個小時的時間,我就有曾有花2個小時坐在電腦前面等待450個 Selenium測試運行通過的悲慘經歷。因此合理有效地規劃Selenium測試就顯得格外的迫切和重要了。而目前比較行之有效的辦法,往大了說,可以叫基于領域的Web測試(Domain Based Web Testing),具體來講,就是Page Object Pattern。
Page Object Pattern里有四個基本概念:Driver、Page、Navigator和Shortcut等。Driver是測試真正的實現機制,比如 Selenium,比如Watir,比如HttpUnit。它們懂得如何去真正執行一個Web行為,通常包含像Click、Select、Type等這樣的表示具體行為的方法;Page是對一個具體頁面的封裝,它們了解頁面的結構,知道諸如id、name、class和xpath這類實現細節,并描述用戶可以在其上進行何種操作;Navigator則代表了URL,表示一些不經頁面操作的直接跳轉;最后Shortcut就是helper方法了,需要看具體的需要而定。下面來看一個超級簡單的例子——測試登錄頁面。
1. Page Object
假設我們使用一個單獨的登錄頁面進行登錄,那么可能會將登錄的操作封裝在一個名為LoginPage的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 endend
login_as是一個具有業務含義的頁面行為。在login_as方法中,page object負責通過依靠id、xpath、name等信息完成登錄操作。在測試中,我們可以這樣來使用這個page object:
page = LoginPage.new $seleniumpage.login_as :name => 'xxx', :password => 'xxx'
不過既然用了Ruby,總要用一些ruby sugar吧,我們定義一個on方法來表達頁面操作的環境:
def on page_type, &block page = page_type.new $selenium page.instance_eval &block if block_given?end
之后我們就可以使用page object的類名常量和block描述在某個特定頁面上操作了:
on LoginPage do login_as :name => 'xxx', :password => 'xxx'end
除了行為方法之外,我們還需要在page object上定義一些獲取頁面信息的方法,比如獲取登錄頁面的歡迎詞的方法:
def welcome_message @driver.get_text 'xpath=
'end
這樣測試也可表達得更生動一些:
on LoginPage do assert_equal 'Welcome!', welcome_message login_as :name => 'xxx', :password => 'xxx'end
當你把所有的頁面都用Page Object封裝了之后,就有效地分離了測試和頁面結構的耦合。在測試中,只需使用諸如login_as和add_product_to_cart這樣的業務行為,而不必依靠像id、name等這些具體且易變的頁面元素了。當這些頁面元素發生變化時,只需修改相應的page object就可以了,而原有測試基本不需要太大或太多的改動。
2. Assertation
只有行為還構不成測試,我們還要判斷行為結果,并進行一些斷言。簡單回顧一下上面的例子,會發現還有一些很重要的問題沒有解決:我怎么判斷登錄成功了呢?我如何才能知道真的是處在登錄頁面了呢?如果我調用下面的代碼會怎樣呢?
$selenium.open url_of_any_page_but_not_loginon LoginPage {
}
因此我們還需要向page object增加一些斷言性方法。至少,每個頁面都應該有一個方法用于判斷是否真正地達到了這個頁面,如果不處在這個頁面中的話,就不能進行任何的業務行為。下面修改LoginPage使之包含這樣一個方法:
LoginPage.class_eval do include Test::Unit::Asseration def visible? @driver.is_text_present(
) && @driver.get_location ==
endend
在visible?方法中,我們通過對一些特定的頁面元素(比如URL地址,特定的UI結構或元素)進行判斷,從而可以得之是否真正地處在某個頁面上。而我們目前表達測試的基本結構是由on方法來完成,我們也就順理成章地在on方法中增加一個斷言,來判斷是否真的處在某個頁面上,如果不處在這個頁面則不進行任何的業務操作:
原文轉自:http://www.anti-gravitydesign.com