2026/3/29 17:21:22
网站建设
项目流程
网站开发 需求,一个网站怎么做新闻模块,网站SEO做点提升流量万象,安徽建工招采平台在自动化测试框架中#xff0c;我们经常使用POM#xff08;Page Object Model#xff09;模式对被测试页面进行封装抽象。
然而#xff0c;在实际开发过程中#xff0c;我们常常看到由于对 POM 理解不深或实践方式不当#xff0c;导致测试代码结构逐渐偏离其初衷#x…在自动化测试框架中我们经常使用POMPage Object Model模式对被测试页面进行封装抽象。然而在实际开发过程中我们常常看到由于对 POM 理解不深或实践方式不当导致测试代码结构逐渐偏离其初衷使得自动化测试框架反而变得臃肿、难以维护。本文将梳理POM的常见反模式Anti-Pattern帮助你在项目中更好地实践和落地POM模式。什么是页面对象模型POMPOM本质上是一种设计模式用于创建网页元素的对象库。其核心目的是建立机制为框架提供更健壮、稳定和灵活的设计。在页面对象类的开发时我们为不同的页面创建一个对应的页面类每个类中封装该页面上的元素定位器以及常用操作方法。这种方式实现了页面元素与测试逻辑的解耦多个测试脚本可以复用同一页面类中的元素与方法从而大大减少了代码重复提升了整体的可维护性与扩展性。为什么使用POM01 可维护性在 Web 应用的开发过程中如果测试代码直接去操作页面上的对象一旦 UI 发生变动测试用例就可能全部失效就会造成散弹式的修改维护成本很高。假设有多个不同的登录测试用例如不同用户名、密码错误、验证码验证等。一旦页面中的某个元素发生改变或者登录页面多了一个验证框时、所有涉及登录的测试用例都需要修改。POM对被测试页面进行抽象和封装将页面元素和操作方法交互逻辑集中在一个类中。当 UI 发生变化时只需要修改对应的Page类从而提升测试用例代码的可维护性。02 代码复用性POM通过封装页面上的操作可以使不同的测试脚本可以复用相同的交互逻辑减少代码重复。例如多个测试用例可能都需要登录功能如果每个测试都手动输入用户名和密码代码会大量冗余。但使用 POM可以封装 login() 方法在不同的测试中直接调用提高代码复用性。03 让测试逻辑与UI 结构解耦使用了POM设计后就间接的为测试框架引入了分层设计的概念上层的测试逻辑和底层的页面解耦。这种解耦方式使得测试层的代码可以专注于测试本身而不依赖具体的操作或元素。POM的设计原则单一职责原则SRP每个页面对象类应只负责一个页面或页面的一小部分功能。这样可以保持代码结构清晰、有条理便于维护同时明确不同功能区域的职责边界。抽象性Abstraction页面对象应屏蔽页面交互的细节例如元素定位和具体操作方法。通过提供更直观、可读性强的接口抽象能够降低测试代码的脆弱性并提升其可维护性。封装性Encapsulation页面对象应封装页面的状态与行为使开发者可以轻松理解当前页面的状态以及可执行的操作。这有助于保持关注点分离并提高测试代码的可读性。可复用性Reusability页面对象应被设计为可在多个测试中复用从而减少代码重复提高测试开发的效率。通过构建模块化、自包含的页面对象可以像积木一样快速组合测试逻辑。易读性Easy to Understand页面对象中的方法和变量命名应具备良好的可读性和表达力让开发者无需阅读底层代码即可理解其作用。清晰的命名有助于提升测试代码的可读性与可维护性。关注点分离Separation of Concerns测试脚本应专注于描述页面的高层行为而页面对象则负责底层的页面操作细节。实现这种职责分离可以让测试代码更清晰、更易组织同时构建出更具可扩展性的测试框架。POM中的常见的Anti Pattern01 在 Page Object 中添加断言或验证逻辑最最常见的一个习惯估计就是在POM中加入断言了比如登录方法中直接写上判断登录是否成功。因为这样似乎就能复用了但是等一下在页面对象方法中包含断言模糊了关注点。页面对象的职责应仅限于封装页面上的元素操作而断言属于测试验证逻辑应该由测试用例负责。将断言写在页面类中会导致职责混乱降低测试用例的灵活性与可维护性。❌ 不推荐的做法在页面对象中断言# page_objects.pyclass LoginPage:def __init__(self, driver):self.driver driverself.username_input driver.find_element_by_id(username)self.password_input driver.find_element_by_id(password)self.login_button driver.find_element_by_id(login_button)self.welcome_message driver.find_element_by_id(welcome_message)def login(self, username, password):self.username_input.send_keys(username)self.password_input.send_keys(password)self.login_button.click()assert Welcome in self.welcome_message.text, Login failed✅ 推荐的做法将断言放在测试用例中# page_objects.pyclass LoginPage:def __init__(self, driver):self.driver driverself.username_input driver.find_element_by_id(username)self.password_input driver.find_element_by_id(password)self.login_button driver.find_element_by_id(login_button)def login(self, username, password):self.username_input.send_keys(username)self.password_input.send_keys(password)self.login_button.click()def get_welcome_message(self):return self.driver.find_element_by_id(welcome_message).text# test_login.pydef test_login_success():login_page LoginPage(driver)login_page.login(user123, password123)welcome_message login_page.get_welcome_message()assert Welcome in welcome_message, Login failed将断言逻辑混入页面对象POM中主要带来的问题1. 限制用例扩展影响多场景测试就拿登录举例当需要测试多个登录场景成功、密码错误、用户名为空等但断言已经写死在 login() 方法中导致你不得不复制代码或创建多个方法如 login_with_valid_credentials()、login_with_invalid_password()这反而增加了代码重复和维护成本。2. 造成不必要的失败假设你有一个测试流程是创建用户 → 删除用户。测试的重点是验证删除功能是否正常但如果在“创建用户”这一步就加了断言验证欢迎信息等一旦这个验证因为意外失败(比如延迟)就会阻断后续步骤让真正想测试的删除功能无从验证。def test_create_and_delete_user():login_page.login(admin, admin123)user_page.create_user(new_user) # 创建时断言意外失败user_page.delete_user(new_user) # 没有执行到这里3. 引入复杂的验证逻辑或外部依赖有时验证结果需要调用接口、查数据库、检查后端日志等这些属于测试逻辑范畴不应混入页面类中。如果你把这类逻辑塞入 Page 类不但代码臃肿还会造成页面类对测试环境有依赖, 违背了测试框架的分层设计。def login(self, username, password):# ❌ 页面对象中引入数据库验证db_user query_user_from_db(username)assert db_user.active, User not activated02 在测试函数中直接操作元素在 Page Object 模式中一个关键的最佳实践是不要在测试脚本中直接使用 locator 来操作页面元素。相反应当在页面对象类中封装好所有的元素定位与相关操作逻辑。❌ 不推荐示例没有封装元素或操作逻辑# Playwright 示例class LoginPage:def __init__(self, page):self.page pageself.email_field page.locator(idinput-email)self.password_field page.locator(idinput-password)self.login_button page.locator(input:has-text(Login))self.welcome_message page.locator(#welcome)# test_login.pydef test_login_success(page):login_page LoginPage(page)login_page.email_field.fill(userexample.com) # ❌ 操作在测试中进行login_page.password_field.fill(password123) # ❌login_page.login_button.click() # ❌assert login_page.welcome_message.is_visible() #这种方式会破坏POM对象的封装性和抽象性背离了设计初衷。我们会将所有的操作细节暴露或转移到了测试层中会让测试用例不仅要描述“测试逻辑”还必须处理“操作细节”从而使测试代码变得冗长、难以阅读和维护。想象一下当一个测试用例需要执行多个步骤、操作多个页面元素时这种将所有操作展开在测试层的方式会让测试脚本变得非常臃肿。✅ 推荐做法封装元素定位与操作逻辑✔️ Selenium 示例# login_page.pyfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.remote.webdriver import WebDriverclass LoginPage:def __init__(self, driver: WebDriver):self.driver driverself._username_locator (By.ID, username)self._password_locator (By.ID, password)self._login_button_locator (By.ID, login)def login(self, username, password):self.driver.find_element(self._username_locator).send_keys(username) self.driver.find_element(self._password_locator).send_keys(password)self.driver.find_element(*self._login_button_locator).click()def is_logged_in(self) - bool:return Welcome in self.driver.page_source# test_login.pydef test_login_success(driver):login_page LoginPage(driver)login_page.login(userexample.com, password123)assert login_page.is_logged_in()✔️ Playwright 示例# login_page.pyfrom playwright.sync_api import Pageclass LoginPage:def __init__(self, page: Page):self.page pageself._email_field page.locator(idinput-email)self._password_field page.locator(idinput-password)self._login_button page.locator(input:has-text(Login))self._welcome_message page.locator(#welcome)def enter_email(self, email: str):self._email_field.fill(email)def enter_password(self, password: str):self._password_field.fill(password)def click_login(self):self._login_button.click()def login(self, email: str, password: str):self.enter_email(email)self.enter_password(password)self.click_login()def is_logged_in(self) - bool:return self._welcome_message.is_visible()# test_login.pydef test_login_success(page):login_page LoginPage(page)login_page.login(userexample.com, password123)assert login_page.is_logged_in()这边说明一下在Python中虽然没有通过关键字强制私有属性的设计。可以通过在属性前加下划线如 _email_field表示该属性是私有内部使用的调用者应该遵守约定不应该类外直接访问感谢每一个认真阅读我文章的人礼尚往来总是要有的虽然不是什么很值钱的东西如果你用得到的话可以直接拿走这些资料对于【软件测试】的朋友来说应该是最全面最完整的备战仓库这个仓库也陪伴上万个测试工程师们走过最艰难的路程希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取