深入理解PO模型


無論是手工測試還是自動化測試,最核心的任務就是編寫測試用例、執行測試用例、輸出測試報告以及維護測試用例。因此,如何提高自動化測試效率就等同於如何提高自動化編寫、執行、維護測試用例的效率。當下最流行的PO模型和關鍵字驅動模型都是為了解決此問題,本文主要介紹一下PO模型

PO模型介紹

全稱PageObject,也叫做POM模型,它是一種設計思想,不是一種規范,是為了解決自動化測試過程中隨代碼量的增加導致代碼冗余,難以維護、難以擴展等事件的方案。在PO模型下,每個頁面都對應到一個類中,每一個類都維護着該頁面中的元素集和操作這些元素的方法,使用此模型的目的是使架構解耦合,讓程序松耦合

案例演示

以登錄為例,文件no_PO_Object.py,使用PO模型對代碼進行優化

# no_PO_Object.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import pytest

login = [["","654321","賬號不能為空"],["test","123456","用戶名不正確。"],
         ["admin","","密碼不能為空"],["admin","654321","用戶名或密碼不正確"],["admin","123456","用戶中心"]]

class TestUser(object):
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        
    def teardown_class(self):
        self.driver.quit()

    @pytest.mark.parametrize("username,pwd,expected",login)
    def test_user_login(self,username,pwd,expected):
        self.driver.get("http://192.166.66.22:8080/user/login")

        # 清空輸入框后輸入用戶名
        self.driver.find_element(By.NAME, "user").clear()
        self.driver.find_element(By.NAME, "user").send_keys(username)
        # 清空輸入框后輸入密碼
        self.driver.find_element(By.NAME, "pwd").clear()
        self.driver.find_element(By.NAME, "pwd").send_keys(pwd)
        # 點擊【登錄】
        self.driver.find_element(By.CLASS_NAME, "btn").click()
        if username == "admin" and pwd == "123456":
            # 等待頁面加載
            WebDriverWait(self.driver, 3).until(EC.title_is(expected))
            # 驗證是否登錄成功,根據當前頁面標題進行判斷
            assert self.driver.title == expected
        else:
            # 等待頁面加載
            WebDriverWait(self.driver, 3).until(EC.alert_is_present())
            alert = self.driver.switch_to.alert
            # 驗證報錯信息是否正確
            assert alert.text == expected
            alert.accept()

將代碼中容易變動的信息都拿出來,單獨寫個類中loginPage.py,若前端屬性值、元素或定位方式發生改變,只需要在此模塊進行更改

# loginPage.py
from selenium.webdriver.common.by import By
class loginPage():
    # 定義一個構造方法,初始化變量,在實例化時傳入一個driver參數
    def __init__(self,driver):
        self.driver = driver

    # 抽出定位和元素的屬性值
    user = (By.NAME, "user")
    pwd = (By.NAME, "pwd")
    btn = (By.CLASS_NAME, "btn")
    url = "http://192.166.66.22:8080/user/login"

    # 加載項目地址
    def user_loginURL(self):
        self.getURL(self.url)

    # 元素操作
    # 清空輸入框后輸入用戶名
    def user_input(self,username):
        self.driver.find_element(*loginPage.user).clear()
        self.driver.find_element(*loginPage.user).send_keys(username)
    # 清空輸入框后輸入密碼
    def pwd_input(self,pwd):
        self.driver.find_element(*loginPage.pwd).clear()
        self.driver.find_element(*loginPage.pwd).send_keys(pwd)
    # 點擊登錄按鈕
    def btn_click(self):
        self.driver.find_element(*loginPage.btn).click()
        
    # 綁定業務函數
    # 對於有關聯關系的操作可以進行業務綁定,比如登錄
    def login(self,username,pwd):
        self.user_loginURL()
        self.user_input(username)
        self.pwd_input(pwd)
        self.btn_click()

測試用例loginCase.py的類中直接調用loginPage.py中的方法

# loginCase.py
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import pytest
from PO.BasePage.loginPage import loginPage

login = [["","654321","賬號不能為空"],["test","123456","用戶名不正確。"],
         ["admin","","密碼不能為空"],["admin","654321","用戶名或密碼不正確"],["admin","123456","用戶中心"]]

class TestUser(object):
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
    
    def teardown_class(self):
        self.driver.quit()

    @pytest.mark.parametrize("username,pwd,expected",login)
    def test_user_login(self,username,pwd,expected):

        # 把用例層面實例的driver傳到loginPage中
        self.login = loginPage(self.driver)

        self.login.loginURL()			# 訪問項目地址
        self.login.user_input(username)	# 輸入用戶名
        self.login.pwd_input(pwd)		# 輸入密碼
        self.login.btn_click()			# 點擊登錄
        # 上面loginPage.py中的業務綁定函數,此處可直接調用,省去調用上面4步單獨的元素調用
        # self.login.login(username,pwd)

        if username == "admin" and pwd == "123456":
            # 等待頁面加載
            WebDriverWait(self.driver, 3).until(EC.title_is(expected))
            # 驗證是否登錄成功,根據當前頁面標題進行判斷
            assert self.driver.title == expected
        else:
            # 等待頁面加載
            WebDriverWait(self.driver, 3).until(EC.alert_is_present())
            alert = self.driver.switch_to.alert
            # 驗證報錯信息是否正確
            assert alert.text == expected
            alert.accept()

至此代碼就實現了下圖中的轉變方式,no_PO_Object.py轉為loginPage.py+loginCase.py

通過上面的修改可以看到,在loginPage.py中出現重復的定位元素操作,若頁面元素較多,或進行多個頁面操作時,會出現更多的重復定位操作,所以創建basePage.py,對頁面操作進一步優化,抽取頁面中的公共方法

# basePage.py
class Browser_init():
    def __init__(self,driver):
        self.driver = driver

    # 加載項目地址
    def getURL(self,url):
        self.driver.get(url)

    # 元素定位
    def locate_ele(self,locator):
        ele = self.driver.find_element(*locator)
        return ele

    # 元素文本清空
    def locate_clear(self,locator):
        self.locate_ele(locator).clear()

    # 元素文本輸入
    def locate_input(self,locator,text):
        self.locate_ele(locator).send_keys(text)

    # 元素點擊
    def locate_click(self,locator):
        self.locate_ele(locator).click()

loginPage.py中的代碼修改如下

# loginPage.py
from selenium.webdriver.common.by import By
from PO.BasePage.basePage import Browser_init

class loginPage(Browser_init):
    
    # 抽出定位和元素的屬性值
    user = (By.NAME, "user")
    pwd = (By.NAME, "pwd")
    btn = (By.CLASS_NAME, "btn")
    url = "http://192.166.66.22:8080/user/login"

    # 加載項目地址
    def user_loginURL(self):
        self.getURL(self.url)

    # 元素操作
    # 清空輸入框后輸入用戶名
    def user_input(self,username):
        self.locate_clear(loginPage.user)
        self.locate_input(loginPage.user,username)
    # 清空輸入框后輸入密碼
    def pwd_input(self,pwd):
        self.locate_clear(loginPage.pwd)
        self.locate_input(loginPage.pwd,pwd)

    # 點擊登錄按鈕
    def btn_click(self):
        self.locate_click(loginPage.btn)

    # 綁定業務函數
    def login(self,username,pwd):
        self.user_loginURL()
        self.user_input(username)
        self.pwd_input(pwd)
        self.btn_click()

loginCase.py的代碼如下

# loginCase.py
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
import pytest
from PO.BasePage.loginPage import loginPage

login = [["","654321","賬號不能為空"],["test","123456","用戶名不正確。"],
         ["admin","","密碼不能為空"],["admin","654321","用戶名或密碼不正確"],["admin","123456","用戶中心"]]

class TestUser(object):
    def setup_class(self):
        self.driver = webdriver.Chrome()
        self.driver.maximize_window()
        
    def teardown_class(self):
        self.driver.quit()

    @pytest.mark.parametrize("username,pwd,expected",login)
    def test_user_login(self,username,pwd,expected):

        # 把用例層面實例的driver傳到loginPage中
        self.login = loginPage(self.driver)

        self.login.login(username,pwd)

        if username == "admin" and pwd == "123456":
            # 等待頁面加載
            WebDriverWait(self.driver, 3).until(EC.title_is(expected))
            # 驗證是否登錄成功,根據當前頁面標題進行判斷
            assert self.driver.title == expected
        else:
            # 等待頁面加載
            WebDriverWait(self.driver, 3).until(EC.alert_is_present())
            alert = self.driver.switch_to.alert
            # 驗證報錯信息是否正確
            assert alert.text == expected
            alert.accept()

最終,源代碼一分為三,可理解為基礎層、頁面層和業務層

  • 基礎層:BasePage,封裝了基礎的Selenium的原生方法,如定位元素、輸入輸出、元素點擊等一些控件操作
  • 頁面層:loginPage,存放一些封裝好的功能用例模塊,繼承基礎層中的操作
  • 業務層:loginCase,真正的測試用例的操作,用例的業務邏輯以及數據驅動,調用頁面層中的方法

三者關系如圖所示

通過以上對代碼的拆分,可以看出PO模型的優缺點:

優點:

  • 提升代碼可維護性,PO模型是一種業務流程與頁面元素操作分離的模式,使得測試代碼更加清晰整潔,利於代碼的可維護性
  • 提高代碼可讀性,因為PO模型對代碼進行了分層,而且也會集中管理一個頁面內的公共方法,利於用例編寫及代碼可讀性
  • 提升代碼復用性,頁面對象和用例之間的相互分離,使得測試人員可集中管理元素對象

缺點:

  • 由於根據項目流程進行了模塊化處理, 造成項目結構復雜化

當然,對於以上代碼仍可以進行細分,所以再次提醒,PO模型是一種思想,不是一種規范,每個人寫出來的方式可能都不一樣,沒有對錯之分,對於PO模型仁者見仁,智者見智


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM