無論是手工測試還是自動化測試,最核心的任務就是編寫測試用例、執行測試用例、輸出測試報告以及維護測試用例。因此,如何提高自動化測試效率就等同於如何提高自動化編寫、執行、維護測試用例的效率。當下最流行的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模型仁者見仁,智者見智
