PO模式完整实现与最佳实践
本节通过完整的测试代码示例展示PO模式的实际应用,并总结PO模式的最佳实践指南。
完整实现示例
测试代码示例
Python
import unittest
from selenium import webdriver
class TestLogin(unittest.TestCase):
"""登录功能测试"""
def setUp(self):
"""测试前准备"""
self.driver = webdriver.Chrome()
self.login_proxy = LoginProxy(self.driver)
def tearDown(self):
"""测试后清理"""
self.driver.quit()
def test_valid_login(self):
"""测试有效登录"""
success, message = self.login_proxy.login_with_valid_credentials()
self.assertTrue(success, message)
print(f"✅ {message}")
def test_invalid_login(self):
"""测试无效登录"""
success, message = self.login_proxy.login_with_invalid_credentials()
self.assertTrue(success, message)
print(f"✅ {message}")
def test_login_with_remember_me(self):
"""测试记住我功能"""
success, message = self.login_proxy.login_with_remember_me()
self.assertTrue(success, message)
print(f"✅ {message}")
def test_admin_quick_login(self):
"""测试管理员快速登录"""
success, message = self.login_proxy.quick_login("admin")
self.assertTrue(success, message)
print(f"✅ {message}")
if __name__ == '__main__':
unittest.main(verbosity=2)
对比分析
使用PO模式前
Python
def test_login():
driver = webdriver.Chrome()
driver.get("https://example.com/login")
driver.find_element(By.ID, "username").send_keys("testuser")
driver.find_element(By.ID, "password").send_keys("testpass")
driver.find_element(By.ID, "login-btn").click()
assert "dashboard" in driver.current_url
driver.quit()
使用PO模式后
Python
def test_login():
driver = webdriver.Chrome()
login_proxy = LoginProxy(driver)
success, message = login_proxy.login_with_valid_credentials()
assert success, message
driver.quit()
复杂业务场景示例
电商网站完整购物流程
Python
class ECommerceProxy:
"""电商网站业务代理类"""
def __init__(self, driver):
self.driver = driver
self.login_proxy = LoginProxy(driver)
self.product_handle = ProductHandle(driver)
self.cart_handle = CartHandle(driver)
self.checkout_handle = CheckoutHandle(driver)
def complete_purchase_flow(self, username, password, product_name, quantity=1):
"""完整的购买流程"""
try:
# 1. 用户登录
success, message = self.login_proxy.login_with_valid_credentials(username, password)
if not success:
return False, f"登录失败: {message}"
# 2. 搜索产品
self.product_handle.search_product(product_name)
product_found = self.product_handle.is_product_available(product_name)
if not product_found:
return False, f"产品 {product_name} 未找到"
# 3. 添加到购物车
self.product_handle.add_to_cart(product_name, quantity)
# 4. 检查购物车
cart_items = self.cart_handle.get_cart_items()
if product_name not in [item['name'] for item in cart_items]:
return False, "产品未成功添加到购物车"
# 5. 结账
self.checkout_handle.proceed_to_checkout()
order_number = self.checkout_handle.complete_checkout()
return True, f"购买成功,订单号: {order_number}"
except Exception as e:
return False, f"购买流程失败: {str(e)}"
def batch_add_products(self, username, password, product_list):
"""批量添加产品到购物车"""
try:
# 登录
success, message = self.login_proxy.login_with_valid_credentials(username, password)
if not success:
return False, f"登录失败: {message}"
added_products = []
failed_products = []
for product_name, quantity in product_list:
try:
self.product_handle.search_product(product_name)
if self.product_handle.is_product_available(product_name):
self.product_handle.add_to_cart(product_name, quantity)
added_products.append(product_name)
else:
failed_products.append(f"{product_name}(未找到)")
except Exception as e:
failed_products.append(f"{product_name}(添加失败: {str(e)})")
result_message = f"成功添加: {len(added_products)}个产品"
if failed_products:
result_message += f", 失败: {len(failed_products)}个产品"
return True, result_message
except Exception as e:
return False, f"批量添加失败: {str(e)}"
PO模式最佳实践
1. 命名规范
Python
# 页面对象类:以Page结尾
class LoginPage:
"""登录页面对象"""
pass
class ProductSearchPage:
"""产品搜索页面对象"""
pass
# 操作类:以Handle结尾
class LoginHandle:
"""登录操作处理类"""
pass
class ShoppingCartHandle:
"""购物车操作处理类"""
pass
# 业务代理类:以Proxy结尾
class UserManagementProxy:
"""用户管理业务代理类"""
pass
class OrderManagementProxy:
"""订单管理业务代理类"""
pass
# 定位器:全大写,描述性命名
USERNAME_INPUT = (By.ID, "username")
SUBMIT_BUTTON = (By.CSS_SELECTOR, ".submit-btn")
ERROR_MESSAGE_CONTAINER = (By.CLASS_NAME, "error-container")
2. 方法设计原则
Python
class PageHandle:
"""页面操作基类"""
def operation_method(self):
"""操作方法应该返回self,支持链式调用"""
# 执行操作
return self
def get_something(self):
"""获取类方法,返回具体值"""
element = self.page.get_element()
return element.text
def is_something_displayed(self):
"""判断类方法,返回布尔值"""
try:
self.page.get_element()
return True
except:
return False
def wait_for_condition(self, timeout=10):
"""等待类方法,返回self或抛出异常"""
# 等待逻辑
return self
# 链式调用示例
login_handle = LoginHandle(driver)
result = (login_handle
.navigate_to_login_page()
.input_username("testuser")
.input_password("testpass")
.click_login_button()
.wait_for_login_success())
3. 错误处理策略
Python
class LoginProxy:
"""登录业务代理类 - 错误处理示例"""
def safe_login(self, username, password):
"""安全登录方法 - 完整错误处理"""
try:
self.login_handle.navigate_to_login_page()
self.login_handle.input_username(username)
self.login_handle.input_password(password)
self.login_handle.click_login_button()
self.login_handle.wait_for_login_success()
return True, "登录成功"
except TimeoutException:
return False, "页面加载超时,请检查网络连接"
except NoSuchElementException as e:
return False, f"页面元素未找到: {str(e)}"
except WebDriverException as e:
return False, f"浏览器操作失败: {str(e)}"
except Exception as e:
return False, f"未知错误: {str(e)}"
def retry_login(self, username, password, max_attempts=3):
"""重试登录机制"""
for attempt in range(max_attempts):
success, message = self.safe_login(username, password)
if success:
return True, f"登录成功(第{attempt + 1}次尝试)"
if attempt < max_attempts - 1:
time.sleep(2) # 等待2秒后重试
return False, f"登录失败,已尝试{max_attempts}次"
4. 配置管理
Python
class TestConfig:
"""测试配置类"""
# 环境配置
BASE_URL = "https://example.com"
TIMEOUT = 10
# 用户凭据
VALID_USER = {
"username": "testuser",
"password": "testpass"
}
ADMIN_USER = {
"username": "admin",
"password": "admin123"
}
# 页面URL
LOGIN_URL = f"{BASE_URL}/login"
DASHBOARD_URL = f"{BASE_URL}/dashboard"
PRODUCTS_URL = f"{BASE_URL}/products"
class LoginProxy:
"""使用配置的登录代理类"""
def __init__(self, driver):
self.driver = driver
self.config = TestConfig()
self.login_handle = LoginHandle(driver)
def quick_valid_login(self):
"""快速有效登录"""
user = self.config.VALID_USER
return self.login_with_credentials(user["username"], user["password"])
def quick_admin_login(self):
"""快速管理员登录"""
admin = self.config.ADMIN_USER
return self.login_with_credentials(admin["username"], admin["password"])
5. 等待策略优化
Python
class BasePage:
"""基础页面类 - 等待策略"""
def __init__(self, driver, timeout=10):
self.driver = driver
self.wait = WebDriverWait(driver, timeout)
def wait_for_element_visible(self, locator):
"""等待元素可见"""
return self.wait.until(EC.visibility_of_element_located(locator))
def wait_for_element_clickable(self, locator):
"""等待元素可点击"""
return self.wait.until(EC.element_to_be_clickable(locator))
def wait_for_text_present(self, locator, text):
"""等待文本出现"""
return self.wait.until(EC.text_to_be_present_in_element(locator, text))
def wait_for_url_contains(self, url_part):
"""等待URL包含特定字符串"""
return self.wait.until(EC.url_contains(url_part))
class LoginPage(BasePage):
"""继承基础页面的登录页面"""
USERNAME_INPUT = (By.ID, "username")
PASSWORD_INPUT = (By.ID, "password")
LOGIN_BUTTON = (By.ID, "login-btn")
def get_username_input(self):
return self.wait_for_element_visible(self.USERNAME_INPUT)
def get_password_input(self):
return self.wait_for_element_visible(self.PASSWORD_INPUT)
def get_login_button(self):
return self.wait_for_element_clickable(self.LOGIN_BUTTON)
6. 数据驱动测试集成
Python
class DataDrivenLoginProxy:
"""数据驱动的登录代理类"""
def __init__(self, driver):
self.driver = driver
self.login_handle = LoginHandle(driver)
def test_login_with_data_set(self, test_data_list):
"""使用数据集测试登录"""
results = []
for test_data in test_data_list:
username = test_data.get("username")
password = test_data.get("password")
expected_result = test_data.get("expected")
test_description = test_data.get("description", "")
success, message = self.login_handle.attempt_login(username, password)
test_result = {
"description": test_description,
"username": username,
"expected": expected_result,
"actual": success,
"message": message,
"passed": success == expected_result
}
results.append(test_result)
return results
# 使用示例
test_data = [
{
"username": "validuser",
"password": "validpass",
"expected": True,
"description": "有效凭据登录"
},
{
"username": "invaliduser",
"password": "invalidpass",
"expected": False,
"description": "无效凭据登录"
},
{
"username": "",
"password": "validpass",
"expected": False,
"description": "空用户名登录"
}
]
login_proxy = DataDrivenLoginProxy(driver)
results = login_proxy.test_login_with_data_set(test_data)
总结
PO模式的关键要点
- 三层架构:Page层负责元素定位,Handle层负责操作封装,Proxy层负责业务逻辑
- 单向依赖:上层依赖下层,避免循环依赖
- 职责分离:每层专注于自己的职责,保持接口稳定
- 链式调用:支持方法链式调用,提高代码流畅性
- 错误处理:完善的异常处理机制,提高测试稳定性
适用场景
- 大型测试项目:页面数量多,测试用例复杂
- 团队开发:多人协作,需要统一的代码规范
- 长期维护:项目需要长期维护和扩展
- UI变化频繁:页面结构经常变化,需要降低维护成本
实施建议
- 循序渐进:从简单页面开始,逐步应用PO模式
- 团队培训:确保团队成员理解PO模式的设计思想
- 代码评审:通过代码评审确保PO模式实施质量
- 持续重构:根据项目发展持续优化PO模式实现