PO模式在selenium自動化測試框架有什麼好處

和牛發表於2022-03-19

PO模式是在UI自動化測試過程當中使用非常頻繁的一種設計模式,使用這種模式後,可以有效的提升程式碼的複用能力,並且讓自動化測試程式碼維護起來更加方便。

PO模式的全稱叫page object model(POM),有時候叫做 page object pattern。最開始由馬丁福勒提出,這個模式受到selenium自動化測試框架大力推廣,因而成為一種非常主流的自動化測試設計模式。

在PO模式當中,每一個UI頁面使用程式語言當中的類來表示。在這個類當中,通過函式形式定義頁面的行為和操作。這讓呼叫方不需要關注具體執行的操作到底是點選還是拖動,而是關注具體的業務,比如登入、購物等等,甚至如果程式設計師直接把程式碼給產品經理看,他也是能看懂的。

image-20220314141318377

沒有使用PO模式時

在測試用例中直接編寫瀏覽器操作API,對於程式碼編寫者並沒有多高的難度,因為他自己已經對這些API非常熟悉,但是這些瀏覽器操作並不能體現業務,至少沒有產品經理那麼熟悉,因此他很難和產品經理進行溝通,也難和開發溝通,甚至在半個月之後,他已經忘記了自己到底寫了什麼東西。

def test_login_mail(self):
    driver = self.driver
    driver.get("http://www.xxx.xxx.com")
    driver.find_element_by_id("idInput").clear()
    driver.find_element_by_id("xxxxxxx").send_keys("xxxxx")
    driver.find_element_by_id("xxxxxxx").clear()
    driver.find_element_by_id("xxxxxxx").send_keys("xxxxxx")
    driver.find_element_by_id("loginBtn").click()

使用PO模式

使用PO模式有利於梳理業務,也有利於和其他人進行溝通。當你把下面這段程式碼拿給產品經理看的時候,他也大概能知道你測的是什麼業務,能幫你糾正你的測試流程是否正確,或者提出一些更有建設性的意見,這對於大型專案需要頻繁溝通和梳理業務時非常有用。

def test_login_mail(self):
    LoginPage(driver).login()

而瀏覽器本身的操作,就會被分離到一個更底層的模組,這些程式碼你可以不對呼叫方暴露,產品經理並不關心你這個頁面中什麼元素定位,他也不懂。

class LoginPage:
	username_loc=(By.ID,"idInput")
    password_loc =(By.ID,"pwdInput")
    submit_loc =(By.ID,"loginBtn")
    span_loc=(By.CSS_SELECTOR,"div.error-tt>p")
    dynpw_loc =(By.ID,"lbDynPw")
    userid_loc =(By.ID,"spnUid")
    
    
    def __init__(self, driver):
        self.driver = driver
        
    def login(self):
        self.driver.find_element(*self.username_loc).clear()
        self.driver.find_element(*self.username_loc).send_keys("xxxxx")
        self.driver.find_element(*self.password_loc).clear()
        self.driver.find_element(*self.password_loc).send_keys("xxxxxx")
        self.driver.find_element(*self.submit_loc).click()

這種方式把元素定位方式也分離了。但是這種元素定位的表示式可讀性也不是很強,可以換用 property 方式來表示元素,所有的元素統一放在一起,修改起來也比較方便。

class LoginPage:
    def __init__(self, driver)
    	self.driver = driver
        
    @property
    def username_element(self):
        return self.driver.find_element('id', 'idInput')
    
    @property
    def password_element(self):
        return self.driver.find_element('id', 'pwdInput')
    
    @property
    def submit_element(self):
        return self.driver.find_element('id', 'loginBtn')
    
    def login(self, name, password):
        self.username_element.send_keys(name)
        self.password_element.send_keys(password)
        self.submit_element.click()

第三種方式可以充分利用Python的描述符特性,你會發現很多序列化庫或者ORM框架都有類似的用法。

class LoginPage:
    def __init__(self, driver)
    	self.driver = driver
        
    username = Element(css='#idInput', desc='使用者名稱輸入框')
    password = Element(css='#pwdInput', desc='密碼輸入框')
    confirm = Element(css='#loginBtn', desc='登入確認按鈕')
    
   	def login(self, name, password):
        self.username.send_keys(name)
        self.password.send_keys(password)
        self.confirm.click()
        

而 Element 類可以通過 Python 描述符實現,這裡為了方便,只定義了xpath的元素定位方法:

class Element:

    def __init__(self,xpath=None,desc=''):
        self.xpath = xpath
        self.desc = desc

    def __get__(self, instance, owner):
        driver = instance.browser
        el = driver.find_element('xpath', self.xpath)
        return el

PO模式和DDD

PO模式是DDD(領域驅動設計)的一個簡單實現,但是還不夠徹底。如果要在自動化測試中貫徹DDD,我覺得還有一些可以優化的空間。

首先某一個業務不一定只是單個頁面的操作,比如登入不一定只涉及到LoginPage這個頁面,因此直接在LoginPage中編寫login函式就不是很合理。對於呼叫方來說,應該明確說明的是誰在登入,而不是指某個頁面。像這樣:

user.login()
# or
login(user)

我們編寫的程式碼就像是自然語言,任何懂英語的人都知道程式碼在做什麼,在DDD中,叫做領域特定語言(DSL), 要實現這種邏輯,在Page類和呼叫中間應該還會有一個層級來封裝user。

其次,Page頁面會依賴更底層的資源,比如元件,元素型別。因此在 Page 類的下方應該會使用 InputElement, ButtonElement 、SelectElement 這樣的元素類和 HeaderComponent、FooterComponent 這樣的元件類。

class LoginPage:
	username_filed = InputElement('xxx')
	password_filed = PasswordElement('xxx')

領域驅動設計對於大型專案梳理業務、同步業務、溝通業務是非常有幫助的,是一種以業務為中心的設計正規化。PO模式對於DDD的小範圍應用,以及具體了足夠多的好處:

  • 便於維護。每一個頁面的操作都被單獨的存放在一個類檔案中,當前端頁面被修改之後,只需要找到對應類檔案進行修改,其他的程式碼並不需要進行修改,這符合單一職責原則。
  • 便於重複使用。在進行自動化測試的時候,一個測試由多個測試步驟組成,這些測試步驟可能涉及到多個頁面的操作。而用例與用例之間的操作可能重合。PO模式可以重複利用這些測試步驟,簡化程式碼的編寫。
  • 提高了可讀性。頁面的操作都被以函式的形式封裝起來了。函式名就具備註釋的作用,其他人閱讀程式碼時可以通過函式了解業務。

相關文章