• <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>
    • 軟件測試技術
    • 軟件測試博客
    • 軟件測試視頻
    • 開源軟件測試技術
    • 軟件測試論壇
    • 軟件測試沙龍
    • 軟件測試資料下載
    • 軟件測試雜志
    • 軟件測試人才招聘
      暫時沒有公告

    字號: | 推薦給好友 上一篇 | 下一篇

    應用Selenium和Ruby進行面向領域的Web測試

    發布: 2008-7-09 14:45 | 作者: 不詳 | 來源: 51testing | 查看: 111次 | 進入軟件測試論壇討論

    領測軟件測試網 關鍵字:Web測試應用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 end end

    login_as是一個具有業務含義的頁面行為。在login_as方法中,page object負責通過依靠id、xpath、name等信息完成登錄操作。在測試中,我們可以這樣來使用這個page object:

    page = LoginPage.new $selenium page.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_login on LoginPage {...}

             因此我們還需要向page object增加一些斷言性方法。至少,每個頁面都應該有一個方法用于判斷是否真正地達到了這個頁面,如果不處在這個頁面中的話,就不能進行任何的業務行為。下面修改LoginPage使之包含這樣一個方法:

    LoginPage.class_eval do include Test::Unit::Asseration def visible? @driver.is_text_present(...) && @driver.get_location == ... end end

             在visible?方法中,我們通過對一些特定的頁面元素(比如URL地址,特定的UI結構或元素)進行判斷,從而可以得之是否真正地處在某個頁面上。而我們目前表達測試的基本結構是由on方法來完成,我們也就順理成章地在on方法中增加一個斷言,來判斷是否真的處在某個頁面上,如果不處在這個頁面則不進行任何的業務操作:

    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

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

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

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

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

             需要注意的是,雖然我們在page object里引入了Test::Unit::Asseration模塊,但是并沒有在斷言方法里使用任何assert*方法。這是因為,概念上來講page object并不是測試。使之包含一些真正的斷言,一則概念混亂,二則容易使page object變成針對某些場景的test helper,不利于以后測試的維護,因此我們往往傾向于將斷言方法實現為一個普通的返回值為boolean的方法。

    3. Test Data

             測試意圖的體現不僅僅是在行為的描述上,同樣還有測試數據,比如如下兩段代碼:

    on LoginPage do login_as :name => 'userA', :password => 'password' end assert on WelcomeRegisteredUserPage registered_user = {:name => 'userA', :password => 'password'} on LoginPage do login_as registered_user end assert on WelcomeRegisteredUserPage

             測試的是同一個東西,但是顯然第二個測試更好的體現了測試意圖:使用一個已注冊的用戶登錄,應該進入歡迎頁面。我們看這個測試的時候,往往不會關心用戶名啊密碼啊具體是什么,我們關心它們表達了怎樣的測試案例。我們可以通過DataFixture來實現這一點:

    module DataFixture USER_A = {:name => 'userA', :password => 'password'} USER_B = {:name => 'userB', :password => 'password'} def get_user identifier case identifier when :registered then return USER_A when :not_registered then return USER_B end end end

             在這里,我們將測試案例和具體數據做了一個對應:userA是注冊過的用戶,而userB是沒注冊的用戶。當有一天,我們需要將登錄用戶名改為郵箱的時候,只需要修改DataFixture模塊就可以了,而不必修改相應的測試:

    include DataFixtureDat user = get_user :registered on LoginPage do login_as user end assert on WelcomeRegisteredUserPage

             當然,在更復雜的測試中,DataFixture同樣可以使用真實的數據庫或是Rails Fixture來完成這樣的對應,但是總體的目的就是使測試和測試數據有效性的耦合分離:

    def get_user identifier case identifier when :registered then return User.find '....' end end 4.Navigator

             與界面元素類似,URL也是一類易變且難以表達意圖的元素,因此我們可以使用Navigator使之與測試解耦。具體做法和Test Data相似,這里就不贅述了,下面是一個例子:

    navigate_to detail_page_for @product on ProductDetailPage do .... end 5. Shortcut

             前面我們已經有了一個很好的基礎,將Selenium測試與各種脆弱且意圖不明的元素分離開了,那么最后shortcut不過是在蛋糕上面最漂亮的奶油罷了——定義具有漂亮語法的helper:

    def should_login_successfully user on LoginPage do assert_equal 'Welcome!', welcome_message login_as user end assert on WelcomeRegisteredUserPage end

    然后是另外一個magic方法:

    def given identifer words = identifier.to_s.split '_' eval "get_#{words.last} :#{words[0..-2].join '_'}" end

    之前的測試就可以被改寫為:

    def test_should_xxxx should_login_successfully given :registered_user end

    這是一種結論性的shortcut描述,我們還可以有更behaviour的寫法:

    def login_on page_type on page_type do assert_equal 'Welcome!', welcome_message login_as @user end end def login_successfully on WelcomeRegisteredUserPage end def given identifer words = identifier.to_s.split '_' eval "@#{words.last} = get_#{words.last} :#{words[0..-2].join '_'}" end

    最后,測試就會變成類似驗收條件的樣子:

    def test_should_xxx given :registered_user login_on LoginPage assert login_successfully end

    總之shortcut是一個無關好壞,只關乎想象力的東西,盡情揮灑Ruby DSL吧!

    結論

             Selenium是一個讓人又愛又恨的東西,錯誤地使用Selenium會給整個敏捷團隊的開發節奏帶來災難性的影響。不過值得慶幸的是正確地使用Selenium的原則也是相當的簡單:

    通過將脆弱易變的頁面元素和測試分離開,使得頁面的變化不會對測試產生太大的影響。 明確指定測試數據的意圖,不在測試用使用任何具體的數據。 盡一切可能,明確地表達出測試的意圖,使測試易于理解。

             當然,除了遵循這幾個基本原則之外,使用page object或其他domain based web testing技術是個不錯的選擇。它們將會幫助你更容易地控制Selenium測試的規模,更好地平衡覆蓋率和執行效率,從而更加有效地交付高質量的Web項目。

             此文中涉及的都是我最近幾周以來對Selenium測試進行重構時所采用的真實技術。感謝Nick Drew幫助我清晰地劃分了Driver、 Page、Nagivator和Shortcut的層次關系,它們構成我整個實踐的基石;感謝Chris Leishman,在和他結對編程的過程中,他幫助我錘煉了Ruby DSL;還有Mark Ryall和Abhi,是他們第一次在項目中引入了Test Data Fixture,使得所有人的工作都變得簡單起來。

    延伸閱讀

    文章來源于領測軟件測試網 http://www.kjueaiud.com/

    TAG: ruby Ruby selenium Selenium web Web WEB 領域 應用

    21/212>

    關于領測軟件測試網 | 領測軟件測試網合作伙伴 | 廣告服務 | 投稿指南 | 聯系我們 | 網站地圖 | 友情鏈接
    版權所有(C) 2003-2010 TestAge(領測軟件測試網)|領測國際科技(北京)有限公司|軟件測試工程師培訓網 All Rights Reserved
    北京市海淀區中關村南大街9號北京理工科技大廈1402室 京ICP備10010545號-5
    技術支持和業務聯系:info@testage.com.cn 電話:010-51297073

    軟件測試 | 領測國際ISTQBISTQB官網TMMiTMMi認證國際軟件測試工程師認證領測軟件測試網

    老湿亚洲永久精品ww47香蕉图片_日韩欧美中文字幕北美法律_国产AV永久无码天堂影院_久久婷婷综合色丁香五月

  • <ruby id="5koa6"></ruby>
    <ruby id="5koa6"><option id="5koa6"><thead id="5koa6"></thead></option></ruby>

    <progress id="5koa6"></progress>

  • <strong id="5koa6"></strong>