二、装饰器核心原理:函数式编程的"语法糖"
在 UI 自动化测试的实际场景中,页面加载超时、元素定位失败、网络波动等异常问题频繁出现。传统的try-except嵌套方式虽然能捕获异常,但会导致业务代码与处理逻辑高度耦合,造成代码冗余、可读性差等问题。
装饰器作为 Python 的核心特性之一,能够以非侵入式的方式将异常处理逻辑从业务代码中剥离,实现代码解耦与复用。通过装饰器,我们可以将通用的异常处理逻辑封装为可复用的组件,让测试代码更简洁、易维护。
装饰器是Python中函数式编程的重要特性,本质是通过高阶函数实现代码增强,遵循以下核心原则:
- 不修改原函数代码:通过嵌套函数包裹原逻辑
- 透明性:调用者无需感知装饰逻辑
- 复用性:一个装饰器可作用于多个函数
1. 两层装饰器(无参数版)
结构解析:
python
def decorator(func): # 第一层:接收被装饰函数
def wrapper(*args, **kwargs): # 第二层:包装函数(命名为wrapper)
# 前置逻辑(如异常处理)
result = func(*args, **kwargs) # 执行原函数
# 后置逻辑(如返回值处理)
return result
return wrapper # 返回包装函数
执行流程:
python
@decorator # 等价于 func = decorator(func)
def original_func(): pass
2. 三层装饰器(带参数版)
结构解析:
python
def decorator_with_param(param): # 第一层:接收装饰器参数
def decorator(func): # 第二层:接收被装饰函数
def wrapper(*args, **kwargs): # 第三层:包装函数
# 使用装饰器参数(param)和原函数参数(*args)
result = func(*args, **kwargs)
return result
return wrapper
return decorator # 返回第二层函数
执行流程:
python
@decorator_with_param("参数值") # 等价于 func = decorator_with_param("参数值")(func)
def original_func(): pass
关键区别对比
类型 | 层数 | 参数传递时机 | 典型场景 |
---|---|---|---|
无参数装饰器 | 2层 | 装饰器定义时 | 统一日志记录 |
带参数装饰器 | 3层 | 装饰器调用时 | 多场景配置(如重试次数) |
二、装饰器在异常处理中的应用:替代try/catch的优雅方案
在UI自动化中,传统try/catch
会导致测试用例臃肿,通过装饰器可将异常处理逻辑移至框架层,实现:
- 关注点分离:测试用例仅关注业务逻辑
- 集中管理:异常处理逻辑统一维护
- 可配置性:通过参数动态调整处理策略
1. 基础异常处理装饰器(两层结构)
python
from selenium.common.exceptions import WebDriverException
import logging
logger = logging.getLogger("UI_Handler")
def handle_ui_exception(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs) # 执行原操作
except WebDriverException as e: # 捕获UI技术异常
logger.error(f"UI操作失败:{str(e)}")
raise # 抛给上层处理或添加重试
except Exception as e:
logger.error(f"未知异常:{str(e)}")
raise
return wrapper
应用在Page Object层(示例:处理元素不存在异常):
python
from selenium import webdriver
from selenium.webdriver.common.by import By
class LoginPage:
def __init__(self, driver):
self.driver = driver
@handle_ui_exception # 装饰器应用于操作方法
def click_submit(self):
# 可能抛出NoSuchElementException
self.driver.find_element(By.ID, "submit").click()
测试用例层无感知调用:
python
def test_login(driver):
login_page = LoginPage(driver)
login_page.click_submit() # 异常处理在中间层完成
assert DashboardPage(driver).is_displayed()
2. 参数化异常处理装饰器(三层结构)
需求 :不同场景下异常处理策略不同(如重试次数、是否截图)
示例:处理NoSuchElementException并应对业务空值:
python
from selenium.common.exceptions import NoSuchElementException
from functools import wraps
import logging
import time
logger = logging.getLogger("UI_Handler")
def handle_element_exceptions(
retry_times=3,
default_value="N/A" # 业务空值默认返回值
):
"""处理元素定位异常和业务空值的装饰器"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
self = args[0] # 假设第一个参数是Page Object实例
driver = self.driver
# 技术异常处理:重试机制
for attempt in range(retry_times + 1):
try:
result = func(*args, **kwargs)
# 业务空值处理:文本为空时返回默认值
if isinstance(result, str) and len(result.strip()) == 0:
logger.warning("检测到业务空值,返回默认值")
return default_value
return result
except NoSuchElementException as e: # 捕获元素不存在异常
if attempt < retry_times:
logger.error(f"元素未找到(重试{attempt+1}/{retry_times}):{str(e)}")
time.sleep(1) # 重试间隔1秒
else:
logger.error(f"最终失败:{str(e)}")
return default_value # 超过重试次数返回默认值
except Exception as e:
logger.error(f"未知异常:{str(e)}")
return default_value
return wrapper
return decorator
Page Object层应用示例:
python
class SearchPage:
def __init__(self, driver):
self.driver = driver
self.driver.get("https://example.com")
@handle_element_exceptions(retry_times=2, default_value="无搜索结果")
def get_search_result_text(self):
"""获取搜索结果文本,可能返回空字符串或抛出异常"""
element = self.driver.find_element(By.CSS_SELECTOR, "#search-result")
return element.text.strip() # 可能返回空字符串(业务空值)
测试用例层逻辑:
python
def test_search_functionality():
driver = webdriver.Chrome()
search_page = SearchPage(driver)
result = search_page.get_search_result_text()
assert result != "无搜索结果", "搜索功能异常" # 直接断言有效值
driver.quit()
三、避免测试用例冗余的核心原则
1. 三层架构设计
层级 | 职责描述 | 异常处理位置 |
---|---|---|
测试用例层 | 业务流程验证、断言 | 无try/catch |
中间层(Page Object) | 具体UI操作(元素定位、点击) | 装饰器应用层 |
框架底层 | 通用工具(驱动管理、日志、截图) | 装饰器定义层 |
2. 装饰器的优势对比
传统try/catch | 装饰器方案 |
---|---|
测试用例代码臃肿 | 代码简洁,聚焦业务逻辑 |
异常处理逻辑分散 | 集中管理,修改成本低 |
难以复用 | 可复用至所有类似操作 |
四、多项目场景下的装饰器实践:策略模式与动态配置
当多个项目共用自动化框架时,通过策略模式实现差异化异常处理:
1. 定义项目级异常处理策略
python
# project_strategies.py
STRATEGY_A = { # 电商项目:严格重试+默认空值
"retry_times": 3,
"default_value": "商品无库存"
}
STRATEGY_B = { # 后台项目:快速失败+截图
"retry_times": 0,
"default_value": "数据为空",
"screenshot_path": "/error_logs/"
}
2. 装饰器动态加载策略
python
def load_strategy(project_name):
strategies = {
"project_a": STRATEGY_A,
"project_b": STRATEGY_B
}
return strategies.get(project_name, {})
def ui_action_with_strategy(project_name):
strategy = load_strategy(project_name)
return handle_element_exceptions(**strategy) # 解包策略参数
3. 项目级Page Object应用
python
# project_a/pages.py
class ProductPage:
@ui_action_with_strategy("project_a")
def check_stock(self):
return self.driver.find_element(By.CSS_SELECTOR, ".stock").text
# project_b/pages.py
class ReportPage:
@ui_action_with_strategy("project_b")
def get_report_data(self):
return self.driver.find_element(By.ID, "data").text
五、总结:装饰器的核心价值
- 解耦业务逻辑与技术细节:测试用例仅关注"验证什么",框架层处理"如何处理异常"
- 提升可维护性:异常处理逻辑集中在装饰器中,修改时无需触及测试用例
- 支持复杂场景:通过参数化和策略模式,轻松应对多项目、多场景需求
最佳实践建议:
- 装饰器命名遵循
handle_xxx
或xxx_strategy
规范 - 用
functools.wraps
保留原函数元信息 - 在框架底层统一管理装饰器,避免重复定义
通过装饰器模式,UI自动化测试代码将更简洁、更健壮,真正实现"一次编写,多处复用"的工程化目标。