一文詳解Python中PO模式的設(shè)計(jì)與實(shí)現(xiàn)
在使用 Python 進(jìn)行編碼的時(shí)候,會(huì)使用自身自帶的編碼設(shè)計(jì)格式,比如說(shuō)最常見(jiàn)的單例模式,稍微抽象一些的抽象工廠模式等等… 在利用 Python 做自動(dòng)化測(cè)試的時(shí)候,是不是也有自己的設(shè)計(jì)模式呢?所以在今天這個(gè)小章節(jié)里,需要續(xù)了解的就是 python 作為自動(dòng)化測(cè)試?yán)锩娴囊环N設(shè)計(jì)模式,尤其是 UI自動(dòng)化 的專屬模式 —> “PageObject” 自動(dòng)化設(shè)計(jì)模式,簡(jiǎn)稱 “PO模式” 。
了解并實(shí)現(xiàn) “PageObject” 自動(dòng)化設(shè)計(jì)模式
什么是PO模式
一種在測(cè)試自動(dòng)化中變得流行的設(shè)計(jì)模式,使得自動(dòng)化測(cè)試腳本的代碼量減少,避免代碼重復(fù),更加易讀,減少維護(hù)的成本。
其實(shí)簡(jiǎn)單來(lái)說(shuō)就是將頁(yè)面的操作、腳本的Case、通用的頁(yè)面元素分開(kāi)的這樣一個(gè)模式。
一般 PO 設(shè)計(jì)模式多數(shù)分為三層
PO 三層模式
第一層:(核心、BasePage層)
- 對(duì) Selenium 的底層進(jìn)行二次封裝,定義一個(gè)所有頁(yè)面都繼承的基礎(chǔ)屬性頁(yè)面 —> BasePage 。
- 封裝 Selenium 的基本方法,例如:元素定位、元素等待、導(dǎo)航頁(yè)面、頁(yè)面跳轉(zhuǎn)等等...
- PS:其實(shí)在使用的過(guò)程中不需要全部封裝,用到多少方法就封裝多少方法即可。(之前接觸過(guò)其他大佬的自動(dòng)化框架,他把所有的 selenium 的底層的方法做了一層封裝,這樣做很好,能夠做很多的事情,但是比較繁重。實(shí)際上在真實(shí)使用的時(shí)候用不到那么多,所以不建議全部封裝)。
第二層:(頁(yè)面層、也叫配置層)
- 頁(yè)面元素進(jìn)行分離,每個(gè)元素只定位一次,隔離定位。如果頁(yè)面改變,只需要改變相應(yīng)的元素定位。
- 如果存在一些業(yè)務(wù)的屬性、方法,需要將其通過(guò)業(yè)務(wù)方法的方式將業(yè)務(wù)與操作元素的動(dòng)作分離開(kāi)來(lái)。
第三層:(封裝測(cè)試層)
使用單元測(cè)試框架對(duì)業(yè)務(wù)邏輯進(jìn)行封裝測(cè)試
PO 設(shè)計(jì)模式的優(yōu)點(diǎn)
UI 頁(yè)面的頻繁變化,導(dǎo)致頁(yè)面 UI 元素頻繁的變動(dòng),PO設(shè)計(jì)模式便于元素定位改變的維護(hù)。
傳統(tǒng)線性自動(dòng)化,多個(gè)用例腳本中需要反復(fù)的定位同一個(gè)元素,PO設(shè)計(jì)模式可以減少這部分頻繁定位元素的代碼量
小節(jié):減少重復(fù)代碼的冗余,便于UI頁(yè)面頻繁變更下的元素定位維護(hù)。
將改寫(xiě)的腳本轉(zhuǎn)為PO設(shè)計(jì)模式
首先在項(xiàng)目里創(chuàng)建一個(gè) python package 命名為 pages ,然后在 pages 創(chuàng)建一個(gè)模塊 base_page.py 用來(lái)作為第一層的 base_page核心層 。
如下圖:

構(gòu)建基礎(chǔ)的 BasePage 層
嘗試構(gòu)建最基礎(chǔ)的 base_page 層,代碼示例如下:
# coding:utf-8
from selenium import webdriver
class BasePage(object):
"""
1、第一層 - 核心層-BasePage層,定義一個(gè)所有頁(yè)面都繼承的page層
2、對(duì)將要使用的 selenium 的底層方法進(jìn)行二次封裝
"""
def __init__(self, driver, path=None): # 構(gòu)造函數(shù),類的初始化
"""
為了方便編寫(xiě)將 driver 初始化,
先使用 "self.driver = webdriver.Chrome()" 后續(xù)改為 self.driver = driver
"""
self.driver = webdriver.Chrome()
# self.driver = driver
self.driver.implicitly_wait(5) # 定義全局的默認(rèn)加載時(shí)間
self.load_page(path) # 訪問(wèn)并加載網(wǎng)頁(yè)
def load_page(self, path=None): # 訪問(wèn)并加載網(wǎng)頁(yè),如果 path 不為空的話,直接傳給 driver.get() 訪問(wèn)
if path is not None:
self.driver.get(path)
def by_xpath(self, xpath): # 二次封裝 selenium 的 xpath 元素定位
return self.driver.find_element_by_xpath(xpath)
def js_click(self, xpath): # JavaScript 定位元素,并執(zhí)行 click
self.driver.execute_script('arguments[0].click()', self.by_xpath(xpath))
到這里,base_page 層算是寫(xiě)完了,這就是一個(gè)最底層、最基礎(chǔ)的類,這個(gè)類讓我們實(shí)現(xiàn)了 selenium 底層的 Xpath 定位方法 與 JavaScript 定位元素方法,這些方法能夠幫助我們更好的去完成后續(xù)的定位處理操作。
ok,接下我們?cè)偃ゾ帉?xiě)各個(gè)頁(yè)面層的東西。
構(gòu)建首頁(yè)的 Page 層(HomePage)
代碼示例如下:
# coding:utf-8
from selenium import webdriver
from pages.base_page import BasePage # 導(dǎo)入 base_page 層
class HomePage(BasePage): # 定義 FirstPage(繼承 BasePage )
"""
1、第二層 - 各個(gè)頁(yè)面單獨(dú)封裝成層,頁(yè)面的元素、操作、流程
"""
def direct_to_login(self): # 首頁(yè)跳轉(zhuǎn)至登錄頁(yè)
return self.by_xpath("http://*[@id='app']/div[1]/div[5]/div[3]")
def direct_to_product(self): # 登陸成功后,跳轉(zhuǎn)至首頁(yè)
return self.by_xpath("http://*[@id='app']/div[1]/div[5]/div[1]")
# 方法流程
def cross_to_login(self):
self.direct_to_login().click() # 點(diǎn)擊 "登錄" 按鈕進(jìn)行登錄
def cross_to_product(self):
self.direct_to_product().click() # 點(diǎn)擊 "首頁(yè)" 跳轉(zhuǎn)至首頁(yè)
構(gòu)建登錄頁(yè)的 Page 層(LoginPage)
代碼示例如下:
# coding:utf-8
from selenium import webdriver
from pages.base_page import BasePage # 導(dǎo)入 base_page 層
class LoginPage(BasePage): # 定義 FirstPage(繼承 BasePage )
"""
1、頁(yè)面層(登錄頁(yè)) - 各個(gè)頁(yè)面單獨(dú)封裝成層,頁(yè)面的元素、操作、流程
"""
def login_username(self): # 登錄頁(yè) - 用戶名輸入框
return self.by_xpath("http://*[@id='app']/div[1]/form/div[1]/div[2]/div/input")
def login_password(self): # 登錄頁(yè) - 密碼輸入框
return self.by_xpath("http://*[@id='app']/div[1]/form/div[2]/div[2]/div/input")
def login_button(self): # 登錄頁(yè) - 登錄按鈕
return self.by_xpath("http://*[@id='app']/div[1]/form/div[3]/button")
# 登錄Case
def login(self, username, password): # 登錄方法,傳入 username 與 password
self.login_username().send_keys(username)
self.login_password().send_keys(password)
self.login_button().click()
構(gòu)建 首頁(yè) - 訂單 - 支付 流程的 Page 層(OrderPage)
# coding:utf-8
from time import sleep
from pages.base_page import BasePage # 導(dǎo)入 base_page 層
class OrderPage(BasePage): # 定義 FirstPage(繼承 BasePage )
"""
1、頁(yè)面層(登錄頁(yè)) - 各個(gè)頁(yè)面單獨(dú)封裝成層,頁(yè)面的元素、操作、流程
"""
def product(self): # 下單 - 第一個(gè)產(chǎn)品
return self.by_xpath("http://*[@id='app']/div[1]/div[4]/div[2]/a[1]")
def ticket_book(self): # 門(mén)票 - 預(yù)定(按鈕)
return self.by_xpath("http://*[@id='app']/div[1]/div[5]/div[2]/div[2]/a")
def book_date(self): # 門(mén)票 - 選擇日期
return self.by_xpath("http://*[@id='app']/div[1]/form/div[1]/div[1]/div[2]/div/input")
def to_order(self): # 門(mén)票下單
return self.by_xpath("http://*[@id='app']/div[1]/form/div[4]/div/button")
def pay_off(self): # 門(mén)票下單 - 支付
return self.by_xpath("http://*[@id='app']/div[1]/form/div/div/button")
def confirm(self): # 門(mén)票下單 - 確認(rèn)支付
return self.by_xpath("/html/body/div[5]/div[3]/button[2]")
# 下單成功Case
def place_order(self):
self.product().click()
self.ticket_book().click()
self.book_date().send_keys("2022-06-16")
self.to_order().click()
sleep(2)
element = self.pay_off()
self.driver.execute_script('arguments[0].click()', element)
sleep(2)
以上,我們準(zhǔn)備的所有頁(yè)面需要準(zhǔn)備的元素定位、基線流程算是寫(xiě)完了,但是具體的用例,應(yīng)該如何實(shí)現(xiàn)呢?繼續(xù)往下看。
PO 設(shè)計(jì)模式下測(cè)試Case的改造
代碼示例如下:
# coding:utf-8
import unittest
from time import sleep
from selenium import webdriver
from pages.home_page import HomePage
from pages.login_page import LoginPage
from pages.order_page import OrderPage
'''
1、初始化 - 打開(kāi)瀏覽器,設(shè)置瀏覽器大小
2、最終操作 - 關(guān)閉瀏覽器
3、用例部分 - 登錄 與 購(gòu)買操作、下訂單、支付
'''
class TestTravel(unittest.TestCase):
@classmethod
def setUpClass(cls): # 每個(gè)測(cè)試類在加載之前執(zhí)行一次 setUpClass ,初始化方法
cls.driver = webdriver.Chrome()
cls.driver.maximize_window()
def test_a_order(self):
#初始化參數(shù)
username = '13500000001'
password = 'Success@2020'
#初始化界面
home_page = HomePage(driver=self.driver, path="http://django.t.mukewang.com/#/")
login_page = LoginPage(driver=self.driver)
order_page = OrderPage(driver=self.driver)
#跳轉(zhuǎn)登錄
home_page.cross_to_login()
#登錄
login_page.login(username, password)
# 跳轉(zhuǎn)至訂單頁(yè)
home_page.cross_to_product()
#下單
order_page.place_order()
@classmethod
def tearDownClass(cls):
cls.driver.quit() # 徹底退出瀏覽器
if __name__ == '__main__':
unittest.main()這里改造完成之后,記得將 "BasePage 層" 的 '# self.driver = driver' 取消注釋,并將 'self.driver = webdriver.Chrome()' 注釋掉 。
以上就是一個(gè)比較完整的通過(guò) PO 的方式來(lái)連接三個(gè)頁(yè)面與基礎(chǔ)的 base_page 來(lái)寫(xiě)出的更簡(jiǎn)潔一些的測(cè)試用例。
運(yùn)行結(jié)果如下:(速度可能過(guò)快,擔(dān)待一下,gif 只有15秒的時(shí)間)

到此這篇關(guān)于一文詳解Python中PO模式的設(shè)計(jì)與實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Python PO模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python開(kāi)發(fā)之文件操作用法實(shí)例
這篇文章主要介紹了python開(kāi)發(fā)之文件操作用法,以實(shí)例形式較為詳細(xì)的分析了Python針對(duì)文件的路徑、文件名、后綴名等操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
Python數(shù)據(jù)操作方法封裝類實(shí)例
這篇文章主要介紹了Python數(shù)據(jù)操作方法封裝類,結(jié)合具體實(shí)例形式分析了Python針對(duì)數(shù)據(jù)庫(kù)的連接、執(zhí)行sql語(yǔ)句、刪除、關(guān)閉等操作技巧,需要的朋友可以參考下2017-06-06
Django xadmin開(kāi)啟搜索功能的實(shí)現(xiàn)
今天小編就為大家分享一篇Django xadmin開(kāi)啟搜索功能的實(shí)現(xiàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
python使用sorted函數(shù)對(duì)列表進(jìn)行排序的方法
這篇文章主要介紹了python使用sorted函數(shù)對(duì)列表進(jìn)行排序的方法,涉及Python使用sorted函數(shù)的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
淺談pyqt5中信號(hào)與槽的認(rèn)識(shí)
這篇文章主要介紹了淺談pyqt5中信號(hào)與槽的認(rèn)識(shí),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02
python裝飾器-限制函數(shù)調(diào)用次數(shù)的方法(10s調(diào)用一次)
下面小編就為大家分享一篇python裝飾器-限制函數(shù)調(diào)用次數(shù)的方法(10s調(diào)用一次),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-04-04

