PO设计模式详解(Python+selenium+unittest)

本文详细介绍了PageObject(PO)设计模式在软件测试中的应用,包括三层结构、原因、原则以及以统一登录为例的实际操作。PO模式通过封装界面交互,提高测试维护性和应对页面变化的能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、什么是PO设计模式(Page Object Model)

1、Page Object是一种设计模式,它主要体现在对界面交互细节的封装上,使测试用例更专注于业务的操作,从而提高测试用例的可维护性。

2、一般PO设计模式有三层

第一层:

  • 对Selenium 进行二次封装,定义一个所有页面都继承的 BasePage ,
  • 封装 Selenium 基本方法 例如:元素定位,元素等待,导航页面 ,
  • 不需要全部封装,用到多少方法就封装多少方法。

第二层:

  • 页面元素进行分离,每个元素只定位一次,隔离定位,如果页面改变,只需要改变相应的元素定位;
  • 业务逻辑分离 或 操作元素动作分离

第三层:

使用单元测试框架对业务逻辑进行测试

同时,我也准备了一份软件测试视频教程(含接口、自动化、性能等),需要的可以直接在下方观看,或者直接关注VX公众号:互联网杂货铺,免费领取

软件测试视频教程观看处:

2023完整版阿里大牛7天软件测试零基础速成内部教程,从基础到项目实战学完即入职。

二、为什么要使用PO设计模式

  • 页面频繁变化,(页面html结构等变化)导致页面UI元素频繁变动,元素定位改变
  • 传统线性自动化(面向过程开发),用例中需要反复的定位同一个元素
  • 每当页面发生变化的时候,需要在用例中寻找变动的部分,工作量大,容易产生遗漏,不容易维护

三、PO设计模式的六大原则

  • 公共方法代表页面提供的服务
  • 不要暴露细节
  • 不要在封装的框架中做断言
  • 方法可以return到新打开的页面
  • 不要对所有元素建模,仅对自己关注的元素建模
  • 相同的行为会产生不同的结果,可以封装不同的结果

四、PO设计模式实例

以公司的统一登录作为项目例子,用PO设计模式实现登陆:

1、手工用例

2、用PO模式实现自动化用例

项目目录

Base.py

login_page.py

from Page import Base
 
# 创建LoginPage类继续BasePage类
class LoginPage(Base.BasePage):
    '''统一平台登录Page层,登录页面封装操作到的元素'''
    '''第二层:页面元素进行分离,每个元素只定位一次,操作元素动作分离'''
    # 定义url变量,供父类中的open()方法使用
    url ="https://test01....cn/#/login"
    # 用户名输入框定位
    def form_username(self,user_name):
        return self.by_id("name").send_keys(user_name) # 使用了父类的self.by_id()方法定位元素,简洁了不少
    # 密码输入框定位
    def form_password(self,pass_word):
        return self.by_id("password").send_keys(pass_word)
    # 登录按钮定位
    def button_login(self):
        return self.by_xpath("//*[text()='登录']").click()

test_login.py

from Page import login_page
import unittest
from selenium import webdriver
from time import sleep
from selenium.webdriver.common.keys import Keys
from CommonMethod import LogUtil
 
logger = LogUtil.logs() # 调用封装的日志方法,将日志输出到控制台以及写入文件
 
class LoginCase(unittest.TestCase):
    '''第三层:用单元测试框架对业务逻辑进行测试'''
    '''使用LoginPage类及它所继承的父类中的方法'''
    @classmethod
    def setUpClass(cls):
        # 实例化webdriver,俗称:打开浏览器
        cls.driver = webdriver.Firefox(executable_path='E:\\UI test\\UnittestProject\\Driver\\geckodriver.exe')
        cls.driver.implicitly_wait(10)
    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()
    def test_login_success(self):
        page = login_page.LoginPage(self.driver) # 需要用到哪个Page类时,只需要将它传入浏览器驱动,就可以使用该类中提供的方法了
        page.open()
        page.form_username("XXX")
        page.form_password("123456")
        page.button_login()
        sleep(2)
        self.assertEqual(page.get_current_url(), "https://test01....cn/#/home")
        print("登录成功,用例执行结果通过,当前的url为"+ page.get_current_url())
        sleep(1)
    def test_login_fail(self):
        page = login_page.LoginPage(self.driver)
        page.open()
        page.form_username("XXX11")
        page.form_password("123456")
        page.button_login()
        self.assertNotEqual(page.get_current_url(), "https://test01....cn/#/home")
        print("登录失败,用例执行结果通过,当前的url为"+ page.get_current_url())
        page.form_username(Keys.CONTROL+'a') # 输入组合键Ctrl+a,全选输入框内容
        page.form_username(Keys.BACK_SPACE) # 删除键,删除选中的内容
        page.form_password(Keys.CONTROL + 'a')
        page.form_password(Keys.BACK_SPACE)
        sleep(1)
if __name__ == '__main__':
    unittest.main(verbosity=2)

执行结果

在test_login.py中有调用封装的日志方法,这里把封装的日志附上,在CommonMethod目录下的LogUtil.py

五、其他补充

1、相同的行为会产生不同的结果,可以封装不同的结果:在login_page针对【登录】按钮封装了2个方法

2、方法可以return到新打开的页面:在login_page针对【登录】按钮封装,封装了之后要return新页面或其他信息。test_login调用时命名变量来接收这个函数就行了,比如indexurl = page.button_login_success(),在后面断言可以用indexurl变量来跟预期的url断言

 # 登录失败封装
    def button_login_fail(self):
        self.by_xpath("//span[text()='登录']").click()
        toast = self.by_xpath("//p[text()='账号或密码错误!']").text
        return toast
 
    # 登录成功封装
    def button_login_success(self):
        self.by_xpath("//span[text()='登录']").click()
        sleep(2)
        windows = self.driver.window_handles# 获取打开的多个窗口句柄
        self.driver.switch_to.window(windows[-1])# 切换到当前最新打开的窗口
        indexurl = self.get_current_url()
        return indexurl

3、断言:可以通过url、页面标题、text来断言

'''断言跳转的地址,通过try except语句块来进行测试断言,在实际自动化测试脚本开发中,经常要用到处理异常'''
        try:
            self.assertEqual(indexurl,"https://qa-xxxt/add")
            print("点击创建,正确跳转到新页面" + indexurl)
        except AssertionError as msg:
            print("没有跳转到正确页面,当前跳转的地址为"+addurl+"\n报错信息如下"+format(msg))
            '''当断言失败时会抛出异常测试用例执行失败,输出提示信息后重新将异常抛出,即raise,
            若不重新抛出,用例则永远是显示执行成功的,因为它把异常处理掉了'''
            raise msg
  try:
            self.assertEqual(toast, "账号或密码错误!")
            print("登录失败用例场景执行通过,正确弹出提示信息为:" + toast)
        except AssertionError as msg:
            print("错误提示语与预期结果不一致,请检查"+ format(msg))
            raise msg
  try:
            self.assertEqual(toast, "账号或密码错误!")
            print("登录失败用例场景执行通过,正确弹出提示信息为:" + toast)
        except AssertionError as msg:
            print("错误提示语与预期结果不一致,请检查"+ format(msg))
            raise msg

六、写在最后

PS:这套软件测试的自学教程合集。对于在测试行业发展的小伙伴们来说应该会很有帮助。全套内容已经打包到网盘,内容总量接近500个G。如需要软件测试学习资料,关注公众号(互联网杂货铺),后台回复1,整理不易,给个关注点个赞吧,谢谢各位大佬!

这些资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。

### Selenium Page Object 模式简介 Page Object模式是一种用于提高自动化测试可维护性和清晰度的设计模式。通过将页面上的交互行为抽象成类,使得测试代码更加简洁易读[^3]。 具体来说,在Selenium中应用此模式可以: - 将每个网页映射为一个独立的对象 - 把页面元素定义为对象属性 - 页面上发生的操作被建模为方法 这种做法不仅提高了代码重用率,还简化了后续维护工作量。 ### 实施步骤详解 #### 定义页面类 对于每一个待测界面创建相应的Python文件作为其代表性的类。例如针对登录功能构建`LoginPage.py`: ```python from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class LoginPage(object): def __init__(self, driver): self.driver = driver # 定位器 username_locator = (By.ID, "username") password_locator = (By.NAME, "password") login_button_locator = (By.XPATH, "//button[@type='submit']") def enter_username(self, username): element = WebDriverWait(self.driver, 10).until( EC.presence_of_element_located(self.username_locator)) element.send_keys(username) def enter_password(self, password): element = WebDriverWait(self.driver, 10).until( EC.presence_of_element_located(self.password_locator)) element.send_keys(password) def click_login(self): element = WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable(self.login_button_locator)) element.click() ``` 上述代码展示了如何在一个名为 `LoginPage` 的 Python 类里声明三个私有成员变量来保存用户名输入框、密码输入框以及提交按钮的选择器表达式,并提供了相应的方法来进行这些控件的操作[^4]。 #### 编写测试案例 接下来编写具体的测试函数调用上面定义好的页面对象完成整个流程: ```python import unittest from selenium import webdriver from pages.LoginPage import LoginPage class TestLogin(unittest.TestCase): @classmethod def setUpClass(cls): cls.driver = webdriver.Chrome() def test_successful_login(self): login_page = LoginPage(self.driver) self.driver.get('http://example.com/login') login_page.enter_username("test_user") login_page.enter_password("secret_pass") login_page.click_login() # Add assertions here to verify successful login @classmethod def tearDownClass(cls): cls.driver.quit() if __name__ == '__main__': unittest.main() ``` 这段程序首先实例化了一个WebDriver驱动并访问目标网站的登陆页;接着依次填入账号信息并触发登录事件;最后关闭浏览器窗口结束会话[^2]。 ### 最佳实践建议 为了更好地运用Page Object模型开展基于Selenium WebDriver的UI自动化测试项目,这里给出几点推荐的做法: - **保持职责单一原则**:确保每个页面类只负责处理该页面内部的事情。 - **合理划分层次结构**:按照实际业务需求拆分不同级别的组件,如公共头部导航栏、侧边菜单等都应单独建立各自的Page Objects。 - **善用等待机制**:适当引入显式等待策略以应对动态加载的内容,避免因网络延迟等因素造成误判的情况发生[^1]。 - **注重日志记录**:在关键节点处添加详细的调试信息输出,便于排查问题时追踪执行路径[^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值