使用Selenium測試時必需知道的7件事

jopen 9年前發布 | 21K 次閱讀 Selenium

Selenium是一套用于進行瀏覽器自動化測試的開源工具集,可進行Web應用的端到端測試。Selenium主要包括兩個工具:一是Selenium IDE,這是一個在Firefox上運行的插件,可對用戶的行為進行錄制與回放,還可以將錄制的內容生成代碼后在Selenium Remote Control上運行。二是本文的重點Selenium WebDriver(簡稱WebDriver),這是一個開源的項目,能夠讓用戶編寫在各種主流瀏覽器上運行的互操作代碼。目前已經推出了支持C#、 Java等語言的類庫。W3C的WebDriver規范也正是在這個開源項目的基礎上發展起來的。

WebDriver可謂QA工程師進行UI測試最強大的利器,它提供了豐富的API以實現訪問DOM、運行JavaScript、模擬鍵盤輸入等操作。利用WebDriver進行編程可實現UI測試的完全自動化,為回歸測試、乃至持續集成流程提供了極大的便利性。盡管如此,但使用WebDriver 編寫測試需要投入大量的時間,并且由于瀏覽器行為的多樣性,以及UI的易變性,需要進行大量的代碼維護工作。與應用程序的代碼一樣,編寫測試代碼同樣需要遵循良好的代碼規范與設計,糟糕的代碼結構會很快使得測試代碼的維護變成一個無底洞,最終被團隊無奈地拋棄。

在今年的OpenWest 2015大會上,來自Lucidchart的Jared Yarn進行了一場關于Selenium WebDriver測試方面的演講,并隨后撰文總結了演講的內容。他首先談起了所在的團隊在使用WebDriver時所遇到的困境,當時他們維護著由大約 40個不同開發者編寫的300多個測試用例(該團隊沒有專職的測試人員,測試代碼全部由開發者編寫),每天的運行都會產生70個左右的錯誤,這一情況在分配了專門的維護人員之后也沒有多少改善。為了徹底改進測試集的可靠性、可伸縮性以及可維護性,Yarn與整個團隊一起對整個測試代碼結構進行了重構。經過重構后,誤判的失敗率降到了1%以下,并且編寫測試的時間也大大縮短了。

Yarn將這次重構的成功歸結為以下七點。

創建Application User對象

團隊首先要解決的問題是編寫測試所需投入的精力過大,為了克服這一點,他們設計了一些實體對象。首先創建的是一種Application User對象,它代表了網站的后端功能,并且通過一些輔助方法提供了準備測試場景、或是在測試完成前進行teardown(清理)工作的功能。以下是使用這種對象的一個示例:

class EditorPerformanceTest extends LucidSpec {
 val user = new ChartUser

 override def beforeAll() {
   user.login()
   user.createDocument()
 }

 …

 override def afterAll() {
   user.finished()
 }

通過這種對象的應用,所有的準備工作被簡化成兩個方法調用(login與createDocument),而teardown中的邏輯則由finished方法實現,因此開發者可以專注于具體的測試邏輯,將精力集中在bug修復或特性的檢測。

創建Application Driver對象

WebDriver的API非常豐富,單是定位某個UI元素就有不下20種做法,這種巨大的靈活性也令人望而生畏。有數之不盡的方式可以完成拖放、單擊、滾動以及輸入等操作。為了簡化這一點,Yarn的團隊設計了一種Application Driver類,以簡化一些最常見的操作。它首先繼承自WebDriver類,并引用了Selenium中的Actions類,隨后加入了一些方法用于實現最常見的用戶操作,例如單擊元素與執行腳本等等。可以通過下面這個UML圖概括這個類的設計。

其使用方法如下:

def dragAndDrop(cssFrom: String, cssTo: String) {
 val elem1 = getElementByCss(cssFrom)
 val elem2 = getElementByCss(cssTo)
 actions.dragAndDrop(elem1, elem2)
}

def contextClickByCss(css: String) 
 actions.contextClick(getElementByCss(css))
}

通過ID訪問DOM對象

在WebDriver測試過程中,如何定位一個DOM元素是最有挑戰性的任務之一。常見的方式包括XPath、CSS路徑以及各種復雜的CSS選擇器(類似于jQuery),但這些方式在元素移動了位置或改變了CSS類名之后就會失效,不得不重新修改代碼。因此,Yarn建議使用DOM元素的ID進行定位,這種方式的好處是不受元素所在位置、以及所應用的樣式的影響。Yarn的團隊隨后對產品的某一重要特性進行了UI改版,而由于頁面中的ID保持不變,因此測試代碼的改動非常之少。

頁面對象模式

頁面對象模式(Page Object Pattern)是測試代碼可維護性的關鍵因素,這一模式本身非常簡單,它表示每個頁面應了解如何執行該頁面當中的所有操作。舉例來說,登錄頁面知道應當如何提交用戶的認證信息、如何點擊“忘記密碼鏈接”等等操作。如果將這些功能轉移到一個公用的地方,就可以在所有測試中重用這部分功能。以下代碼表示了一個文檔頁面的功能:

object DocsList extends RetryHelper with MainMenu with Page {

 val actionsPanel = new ActionsPanel

 val fileBrowser = new FileBrowser

 val fileTree = new FileTree

 val sharingPanel = new SharingPanel

 val invitationPanel = new InvitationPanel

這個頁面中的操作非常多,因此Yarn將其分解為多個較小的類,每個類都代表了頁面中某個塊的功能。它們各自包含在這一區域內可執行的操作的相關方法,正如以下代碼所示:

def clickCreateDocument(implicit user: LucidUser) {
 doWithRetry() {
   user.clickElement("new-document-button")
 }
}

def selectDocument(fileNum: Int=0)(implicit user: LucidUser) {
 doWithRetry() {
   user.driver.getElements(docIconCss)(fileNum).click()
 }
}

def numberOfDocsEquals(numberOfDocs: Int)(implicit user: LucidUser) : Boolean ={
 predicateWithRetry(WebUser.longWaitTime *5, WebUser.waitTime) {
   numberOfDocuments == numberOfDocs
 }
}

行為的重試

在WebDriver測試過程中,最糟糕的問題在于誤判的錯誤,這為自動化構建過程帶來了很大的困難。對于Yarn的團隊來說,這個問題也是他們所面對的頭號大敵。為了克服這一點,他們為測試加入了重試的功能,使得測試結果得到很大的改善。 以下是這個重試方法的代碼:

/**
* Try and take an action until it returns a value or we timeout
* @param maxWaitMillis the maximum amount of time to keep trying for in milliseconds
* @param pollIntervalMillis the amount of time to wait between retries in milliseconds
* @param callback a function that gets a value
* @tparam A the type of the callback
* @return whatever the callback returns, or throws an exception
*/
@annotation.tailrec
private def retry[A](maxWaitMillis: Long, pollIntervalMillis: Long)(callback: => A): A = {
 val start = System.currentTimeMillis

 Try {
   callback
 } match {
   case Success(value) => value
   case Failure(thrown) => {
     val timeForTest = System.currentTimeMillis - start
     val maxTimeToSleep = Math.min(maxWaitMillis - pollIntervalMillis, pollIntervalMillis)
     val timeLeftToSleep = maxTimeToSleep - timeForTest

     if (maxTimeToSleep <= 0) {        throw thrown      }      else {        if (timeLeftToSleep > 0) {
         Thread.sleep(timeLeftToSleep)
       }
       retry(maxWaitMillis - pollIntervalMillis, pollIntervalMillis)(callback)
     }
   }
 }
}

這段代碼的功能是通過一個簡單的遞歸算法執行所傳入的實際行為,直到該行為成功,或是運行超時為止。以下是使用這個方法的簡單示例:

def numberOfChildren(implicit user: LucidUser): Int = {
 getWithRetry() {
   user.driver.getCssElement(visibleCss).children.size
 }
}

測試集重試

Yarn的團隊所做的最后一項改善是配置測試集的重試,測試集重試會將失敗的測試緩存起來,然后重新運行這些失敗的測試。只要在后續的重試中有一次成功,這項測試就會被認為通過。否則將繼續重試,直到重試次數達到上限為止。 Yarn的做法是盡量將一些依賴于第三方功能的行為區分開來,特意為這些功能的集成編寫非常健壯的代碼似乎沒有什么意義,因此可以將它們放到一個可重試的測試集中。對于他們來說,重試的目的不是為了修復測試代碼中的問題,而是為了消除測試報告中由誤判所帶來的影響。

創造樂趣

Selenium的開發很容易令人感到疲憊,許多測試會無故地失敗,讓這些測試得到正確的結果是非常繁瑣的工作,重復性的樣板代碼令人提不起興致。而在Yarn的團隊建立了一個可靠的、可維護以及可伸縮的框架之后,工作就變得有趣起來了。各種有趣的想法層出不窮,有一位開發者實現了對繪畫 canvas截圖并上傳至Amazon S3服務的功能,隨后又加入了一個截圖比較的工具以實現圖片比較測試。其它令人印象深刻的測試還包括與Google Drive、Yahoo與Google的單點登錄等功能的整合。整個測試工作開始變得生動起來,這也為團隊最終實現了重構的目標帶來了極大的推動力。

來自:http://www.infoq.com/cn/news/2015/07/selenium-7things

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!