對于做
web端
自動化測試的人來說,可能接觸selenium比
QTP還要多,但是我們在做基于selenium的二次開發的時候,經常會說到二次開發是 為了易于維護,很多人可能不懂得維護的價值是什么,和到底要維護什么。今天專門寫一篇關于二次開發的
文章,希望能夠幫到有需要做二次開發的人。
二次開發也就是我們常說的封裝selenium,或者做框架。但是一個框架要包含豐富的類和方法。要有一套完整的體系來幫助我們進行封裝。可以說框架的設 計思想就是整個框架的靈魂,如果設計思想很正確也就意味著這個框架成功了一半,剩下的就是我們怎么樣用程序實現這個思想,在開發的過程中我們也許會用到一 些設計模式和引用一些開源框架。這些只是一個開發人員或者程序設計者的基本素質。至于如果把selenium能夠有效的封裝和一些基本思想,我們來詳細的 了解一下。
在這篇文章里面只針對selenium的webdriver來進行討論,我們不再對rc做任何的解釋和說明。我們都知道webdriver的使用過程中, 貫穿始終的就是一個driver, 并且這個driver代表了一個瀏覽器的當前窗口,我們進行操作的過程中只是進行當前窗口的操作,也就是最這個current window進行的一系列的操作,如果我們需要對打開的新的window來進行操作的話,我們需要switchTo,包括操作frame,當然整個流程下 的操作確實讓我們覺得不是很難編寫,但是我們編寫腳本的過程中需要用到的一些輔助功能可能就會很難的編寫,比如最大化瀏覽器,視角
移動到操作的元素等等, 這個過程一次編寫我們可以做到,但是反復的編寫的話肯定是一個讓人很頭疼的過程,所以這個時候我們要去封裝一些常用的方法,我們有了做一個比較完整的框架 的想法,但是我們忽然又意識到了,這樣的話,我們需要把driver封裝起來,因為整個測試的case都是針對的這個driver,并且只有一個 driver,這樣子的話我們不允許創造多個的driver,也就意味著我們要把自己編寫的小工具類和driver聯系起來,并且我們的測試用例case 類也需要調用這個driver,其實很簡單,我們可以用注入的方式來做,把driver當成tools類的一個屬性值,然后注入到我們的case類中,也 可以通過set的方法來進行操作。有了這些基礎,我們可以防止無限的編寫重復的方法,這樣我們有了自己的工具類。如果說這就是框架的話,就會顯得非常的膚 淺,因為我們寫的這些方法根本沒有任何邏輯可言,只是把需要的方法統統的堆到了一起,所以這個時候我們需要想想用到某些方法來進行分一下層次。
Page類和Window類:
PageObject模式我們都知道,就是把資源都放入到page類里面,然后再編寫邏輯類。這樣的話就可以無限的復用這些資源,這只是籠統的講了一下設 計的思想,至于PageObject到底怎么去實現這些設計呢?我們從webDriver的使用開始入手。webdriver是先從定義瀏覽器開始的。 WebDriver driver = new FirefoxDriver(); 這樣我們就定義了一個firefox的瀏覽器,但是自動化的過程不可能只允許我們把定義瀏覽器的操作放在框架代碼里面,那樣的硬編碼方式使我們的case 不存在可移植性了,如果進行兼容性測試的話,維護起來對這些case的修改量是比較大的,這種硬編碼方式是我們不能夠進行大量維護的,所以我們需要把定義 瀏覽器的過程完全放在case類里面,就是在我們寫
測試用例的時候再去編寫到底用什么瀏覽器,防止在編寫框架的時候硬編碼的形式把瀏覽器寫死在了框架里 面。做到多瀏覽器的可維護性,對于我們進行兼容測試也有一定的幫助,這樣的話我們需要對瀏覽器的選擇部分要進行一定的編碼設計,來完成瀏覽器的可選擇性。 在我們定義完了瀏覽器之后,這個時候我們也許覺得就是開始查找元素了,但是在這個driver的基礎上我們應該發現其實這個時候driver代表的整個頁 面的操作。但是在頁面的操作基礎上我們應該意識到還有一個級別的操作,那就是window的操作,就是針對瀏覽器自身的操作。包括一些基本的返回,向前, 最大化,最小化,或者移動到制定元素的位置,調用js等等等,這些方法的級別是出于window級別的,和頁面無關的。所以我們應該把這些所有的方法都封 裝到單獨的一個層次中,我們暫且稱之為window包中,剛才的瀏覽器的選擇的所有方法我們放browser包中。這樣我們設計出了兩個層次。下面的設計 該如何進行呢?我們知道pageObject的思想是把資源都放入到我們定義的page類里面,所以這個時候我們需要思考了,我們如何設計這里的page 類呢?按照pageObject的思想來看,page類應該是我們自己編寫的,那樣我們的框架是不是就可以放棄編寫page類了呢?直接封裝一些通用的方 法?顯然是不對的,對于頁面html源碼操作的過程中,我們煩透了這些元素查找的硬編碼方式,在一個case里面或者兩個case里面反復的調用編寫是讓 人頭疼的一件事情,所以我們可以把所有的單頁面看做一個層次,里面和PageObject的思想一樣,就是放入了通用的方法,但是它存在的意義不只是這樣 簡單,因為我們操作的過程中需要涉及到frame。并且頁面和頁面之間涉及到不同window之間的切換,所以如何協調window和page之間的關系 成為了我們需要注意的難點和重點,我們知道webdriver里面有一個方法叫做getWindowHandlers,這個方法可以獲得所有的句柄,我們 想設計這個page類那么意味著我們需要去完美的配合window類,他們之間唯一的關聯就是這個方法,所以我們可以設計一個概念,叫做頁面集合,在創建 window對象的時候我們就會自動的出現一個頁面集合器,它的功能就是管理所有的在操作過程中打開的頁面。并且能夠指定一個特殊的page,就是當前頁 面。也就是webdriver中的driver對象,因為它一直都是針對當前頁面編程的。那樣我們的window類里面就存在了兩個屬性,一個收集器,一 個當前頁。這樣我們在window的級別上就能夠完全操作page類了,這樣我們在case類設計的時候,只需要通過window類的級別進行編碼就可以 了,完全可以把page類當作一種資源來處理,我們所有需要的東西都是通過page來獲取的。page類的設計中我們一定要編寫通用的方法。比如獲取 title等等等,最主要的一點就是要進行frame的處理操作,我們如何把frame完美的結合在page里面呢?我們在普通的webdriver腳本 編寫過程中可能反反復復的switchTo的方法讓我們很惱火,并且很難讓我們理解這些腳本到底是想表達什么意思呢?所以我們需要進行對page進行層次 上的小分級,就是把page類再分為一個frame的類和一個模塊的類,因為一個大型頁面里面,通用的結構和模塊是很多的。我們單獨的定義一個模塊類,可 以讓這些相同的結構復用在里面的方法。定義frame類主要是處理把定位到frame的操作從case類中脫離出來,我們編寫到page類里面或者 frame類里面,提供一種方式或者方法來進行frame的定位就可以了,簡單實用,并且簡化case類的編寫,畢竟case類并不是由設計者來編寫,盡 量做到最簡化。當然我們可以成這整個層次都是page類,放在page包里面。
Element類:
Element類就是我們常用的driver.findElement()的那種形式,就是元素類,什么是元素類呢?元素就是我們需要定位的那些東西,我 們在操作過程中很難知道一個findElement到底查找的是什么,因為所有的標簽形式都是通過id或者各種各樣的定位方式來實現的。這個時候我們需要 把各種各樣的findElement都統統的放在一起,造成的腳本難以理解讓后續接手的腳本開發人員可能頭疼不已,所以我們可以做一些簡單的加工。當然我 們還要做的一個最大的
工作就是元素查找的封裝。Element和Page類如何關聯起來。我們知道page和webElement的關聯就是一個 driver.findElement的方法。這樣就可以在頁面上查找元素了。所以我們也在element類中通過這種形式來進行他們之間的關聯。我們通 過在element類中添加定位方式的形式來進行元素定位和page的關聯,我們只需要在編寫自己的page類的過程中直接加入element類就可以 了,element類提供了所有的關于element的方法,比如鼠標事件和鍵盤事件,還有更重要的元素定位方法。定位的方法在這里不做任何的推薦,因為 每個人的思路不同,實現的方式也不同,我個人比較偏向的做法是做一個xml來進行資源的管理,把所有需要的資源都放入到xml里面,這樣我們就可以進行元 素的定位了。并且在后期維護中主要維護xml就可以進行對整個腳本進行維護了,不需要我們大量的重新進行源碼的分析和修改了。當然這是設計的優化過程,因 為定位的實現我們還是需要自己來完成的,我們知道元素的定位方式各種各樣,我們怎么來進行管理和定位呢?我們可以通過map的方法作為屬性值來進行元素的 管理,他的各種定位方法存放在map中,我們需要的時候只需要調一下就可以了。通過觀察源碼findElement也是通過map的形式來進行元素定位存 儲的。我們可以借鑒一下源碼的實現方式。當然我們完全封裝findElement也是可以的。說到這里可能我們有一個問題比較難以解決,那就是層級定位。 如果我們只是給element類添加定位方式的話,那么findElement提供的一級一級的定位方式我們就無法應用了,所以在element類中我們 必要還要提供findElement的方法進行層級定位。這只是為了把webdriver的所有方法都盡量應用到而已。其實通過xpath的方式我們就可 以基本上定位大多數的元素。剩下的內容就是我們對element內容的擴充了。我們可以把元素的更加具體的抽象出來,比如我們把 select,table,checkbox等等等的各種html標簽元素顯式的定義出來,在我們定義一個元素的時候我們能夠更加清晰的看到這個元素的含 義,我們知道它是一個按鈕或者table等等等,這些小元素的操作需要我們自己深入理解和開發,這里不做過多的介紹。
其他類:
通過這些層次的分析我們已經出現一個框架的雛形了,然后我們剩下的設計就是基于完善和優化了。在一個自動化過程中case類是非常重要的,我們需要知道 case類運行結束的結果報告和分析,所以case類的運行等等一系列的東西我們都得有統計,這些東西必須要我們提供一些類來實現,不過所幸的是,強大的 junit或者testng完全可以取代我們要去做的工作,他們可以很完美的提供這些功能,我們只需要介入這些開源包就可以了。我們使用QTP的過程中我 們經常會用到參數化,我們自動化的設計都有了,但是沒有參數化的功能怎么辦?我們應該先想象一下數據提供的方式。testng提供了一種參數化的形式,但 是它是需要在xml里面配置或者硬編碼的形式來進行編寫。不過它提供了一種dataprovider的方式來進行參數化,它傳遞參數的形式是 Object[][],我們可能希望使用參數的時候通過excel表格來完成,這些都是可以實現的,poi包提供了解析excel的功能,非常的強大。我 們可以自己編寫解析類來進行參數化的功能編寫,具體實現不再過多去說。調用的方式就簡單多了,硬性的記住幾個注解就可以了。case類的各種運行我們都有 了,還需要一些什么擴展呢?很顯然就是日志的擴展。日志的設計也是很有技巧的。我們需要用日志監控某一些方法的話,如果前期沒有直接加入日志功能,我們可 以通過spring的方式來進行日志切入操作。但是我們在觀察日志的時候我們通常會希望知道到底運行到哪一步了,我們可以想一想,所有的操作都是基于 element或者window的,page的只是一個抽象出來的概念,所以我們只需要把日志加入到每一個element的方法和window的主要方法 里面就可以監控到整個運行的過程,畢竟我們不能夠去親自盯著屏幕一直。這樣沒個方法不外乎就是運行成功和失敗,所以我們可以通過這種方式來進行編碼。日志 的實現我們可以通過通過的log4j或者自己編寫一個小的日志系統。都是可行的方案。
擴展類:
也許我們需要這些系統能夠有良好的可移植性,我們可以自己編寫類加載器,為以后做整個自動化的測試平臺做準備。最主要都是我們做的這些操作可能需要越來簡 單,所以我們可能會因為引入注解的方式來提供編碼效率,所以我們還需要為注解類做一些輔助的工作。當然這些注解的開發需要我們做足足夠的需求研究,并不是 無謂的去開發各種注解。這些注解的應用應該說很廣泛,不再多說注解的好處。并且注解還有一點可應用的地方就是放在數據庫的操作中,在自動化測試中,其實數 據庫的測試也是一個大的難點。在這里我們只討論前端自動化的設計,不過多的討論別的東西。
前面講到的這些類的存在形式其實就是在框架里面的一種層次,我們談論的這些都是基于webdriver的,并且主要基于前端自動化測試的,當然自動化測試 不只包括這些,還包括服務器端,接口自動化,單元自動化等等。我個人的能力水平也是很有限,可能很多地方說到的不是很到位,希望能夠通過這篇文章能夠給那些希望學習自動化,希望編寫小測試框架的童鞋,一點點的啟發。