1. Page Object設計模式
Page Object是Selenium自動化測試項目開發實踐的最佳設計模式之一,它主要體現在對界面交互細節的封裝,這樣可以使測試方案更關注於業務而非界面細節。從而提高測試案例的可讀性。
1.1 認識Page Object
Page Object設計模式的優點如下:
減少代碼的重復
提高測試用例的可讀性
提高測試用例的可維護性,特別是針對UI頻繁變化的項目。
當為Web頁面編寫測試時,需要操作該Web頁面上的元素。然而,如果在測試代碼中直接操作HTML元素,那么你的代碼是及其脆弱,因為UI經常變動。我們可以將一個page對象封裝成一個HTML頁面,然后通過提供的應用程序特定的API來操作頁面元素,而不是在HTML中四處搜尋。
Page Object原理:
page對象的一個基本經驗法則是:凡是人能做的是,page對象通過軟件客戶端都能夠做到。因此,它也應當提供一個易於編程的接口並隱藏窗口中底層的部分。所以訪問一個文本框應該通過一個訪問方法(accessor method)來實現字符串的獲取與返回,復選框應當使用布爾值,按鈕應當被表示為行為導向的方法名。page對象應當將在GUI控件上所以查詢和操作數據的行為封裝為方法。一個好的經驗法則是,即使改變具體的控件,page對象的接口也不應當發生變化。
盡管該術語是“頁面”對象,但並不意味着想要針對每個頁面建立一個這樣的對象,例如,頁面有重要意義的元素可以獨立為一個page對象。經驗法則的目的是通過給頁面建模,使其對應用程序的使用者變得有意義。
1.2 Page Object實例
下面以登錄126郵箱為例,通過Page Object設計模式來實現。
from selenium import webdriver from selenium.webdriver.common.by import By from time import sleep #創建基礎類 class BasePage(object): #初始化 def __init__(self, driver): self.base_url = 'https://mail.qq.com/' self.driver = driver self.timeout = 30 #定義打開登錄頁面方法 def _open(self): url = self.base_url self.driver.get(url) self.driver.switch_to.frame('login_frame') #切換到登錄窗口的iframe #定義定義open方法,調用_open()進行打開 def open(self): self._open() #定位方法封裝 def find_element(self,*loc): return self.driver.find_element(*loc) #創建LoginPage類 class LoginPage(BasePage): username_loc = (By.ID, "u") password_loc = (By.ID, "p") login_loc = (By.ID, "login_button") #輸入用戶名 def type_username(self,username): self.find_element(*self.username_loc).clear() self.find_element(*self.username_loc).send_keys(username) #輸入密碼 def type_password(self,password): self.find_element(*self.password_loc).send_keys(password) #點擊登錄 def type_login(self): self.find_element(*self.login_loc).click() #創建test_user_login()函數 def test_user_login(driver, username, password): """測試用戶名/密碼是否可以登錄""" login_page = LoginPage(driver) login_page.open() login_page.type_username(username) login_page.type_password(password) login_page.type_login() #創建main()函數 def main(): driver = webdriver.Chrome() username = '' #qq號碼 password = '' #qq密碼 test_user_login(driver, username, password) sleep(3) driver.quit() if __name__ == '__main__': main()
使用自己的賬號密碼登錄,我把代碼中的賬號密碼刪掉了。
1.2.1 創建page類
#創建基礎類 class BasePage(object): #初始化 def __init__(self, driver): self.base_url = 'https://mail.qq.com/' self.driver = driver self.timeout = 30 #定義打開登錄頁面方法 def _open(self): url = self.base_url self.driver.get(url) self.driver.switch_to.frame('login_frame') #切換到登錄窗口的iframe #定義定義open方法,調用_open()進行打開 def open(self): self._open() #定位方法封裝 def find_element(self,*loc): return self.driver.find_element(*loc)
首先創建一個基礎類BasePage,在初始化方法__init__()中定義驅動(driver),基本的URL(base_url)和超時時間(timeout)等。
定義open()方法用於打開URL網站,但它本身並未做這件事情,而是交由_open()方法來實現,而find_element()方法用於元素的定位。
1.2.2 創建LoginPage類
Page類中定義的這些方法都是頁面操作的基本方法。下面根據登錄頁的特點再創建LoginPage類並繼承Page類,這也是Page Object設計模式中最重要的對象層。
#創建LoginPage類 class LoginPage(BasePage): username_loc = (By.ID, "u") password_loc = (By.ID, "p") login_loc = (By.ID, "login_button") #輸入用戶名 def type_username(self,username): self.find_element(*self.username_loc).clear() self.find_element(*self.username_loc).send_keys(username) #輸入密碼 def type_password(self,password): self.find_element(*self.password_loc).send_keys(password) #點擊登錄 def type_login(self): self.find_element(*self.login_loc).click()
LoginPage類中主要對登錄頁面上的元素進行封裝,使其成為更具體的操作方法。例如,用戶名、密碼和登錄按鈕都被封裝成了方法。
1.2.3 創建test_user_login()函數
#創建test_user_login()函數 def test_user_login(driver, username, password): """測試用戶名/密碼是否可以登錄""" login_page = LoginPage(driver) login_page.open() login_page.type_username(username) login_page.type_password(password) login_page.type_login()
test_user_login()函數將單個的元素操作組成一個完整的動作,而這個動作包含了打開瀏覽器,輸入用戶名/密碼、點擊登錄等單步操作。在使用該函數時需要將driver、username、password等信息作為函數的實參,這樣該函數具有很強的可重用性。
1.2.4 創建main()函數
#創建main()函數 def main(): driver = webdriver.Chrome() username = '' #qq號碼 password = '' #qq密碼 test_user_login(driver, username, password) sleep(3) driver.quit() if __name__ == '__main__': main()
main()函數更接近於用戶的操作行為。對用戶來說,要進行郵箱的登錄,需要關心的就是通過哪個瀏覽器打開郵箱網址、登錄的用戶名和密碼是什么,至於輸入框、按鈕是如何定位的,則不需要關心。
這樣分層的好處是,不同的層關心不同的問題。頁面對象層值關心元素的定位問題,測試用例只關心測試的數據。
一個有分歧的地方是page對象是否應自身包含斷言,或者僅僅提供數據給測試腳本設置斷言。在page對象中包含斷言的倡導者認為,這有助於避免在測試腳本中出現重復的斷言,可以更容易的提供更好的錯誤信息,並且提供更接近制作不問風格的API。不再page對象中包含斷言的倡導者則認為,包含斷言會混合訪問頁面數據和實現斷言邏輯的責任,並且導致page對象過於臃腫。
我比較贊成在page對象中不包含斷言,雖然完美可以通過為常用的斷言提供斷言庫的方式來消除重復,提供更好的診斷,但從用戶的角度去自動化的觀點來看,判斷是否登錄成功是用戶需要做的事情,不應該交由頁面對象層來完成。
使用Page Object模式之后的另外一個好處就是有助於降低冗余。如果需要在10個用例中輸入不同的用戶名/密碼登錄,那么用main()方法寫將變得非常簡潔。
因此,Page Object模型的作用在一個測試人員自己寫主場景測試案例時不容易體會到的,因為你不需要和開發、業務交流案例,也不會寫很多重復的動作。但是,當你真正開始測試ATDD或BDD,當你開始寫一些重要的異常分支流程時,當你開始為新需求頻繁維護修改案例時,就會意識到Page Object的作用。
最后,Page Object不是萬靈葯,也不是唯一方案,提高測試案例的可讀性,避免案例步驟冗余才是終極目標。