共计 2426 个字符,预计需要花费 7 分钟才能阅读完成。
传统线性脚本的维护困境
在自动化测试初期,我们往往采用简单的线性脚本编写测试用例。这种方式的典型特征是将操作步骤、断言和数据硬编码在脚本中。但随着业务复杂度提升,这种模式暴露明显缺陷:

- 修改登录逻辑需要全局搜索替换所有相关脚本
- 相同操作逻辑(如商品搜索)在不同用例中重复实现
- 业务调整导致大量用例需要同步修改
- 新人难以理解充满细节的脚本逻辑
主流设计模式对比
PageObject 模式
将页面元素和基础操作封装成类,优点是:
- 元素定位与测试逻辑分离
- 单点修改影响范围可控
但存在页面变动导致大面积修改的问题,且业务流仍然散落在测试脚本中。
ScreenPlay 模式
通过角色、能力、场景等概念组织测试,适合行为驱动开发(BDD)。但学习成本较高,在复杂业务中可能产生过度设计。
Skill 分层架构
核心思想是将测试能力抽象为可组合的技能单元:
- 原子技能层:封装最基础操作(如输入文本、点击按钮)
- 业务技能层:组合原子技能实现业务功能(如登录、下单)
- 测试场景层:通过技能组合构建完整测试流
核心实现(Python 示例)
技能基类设计
class BaseSkill:
"""技能基类,定义公共接口和异常处理"""
def __init__(self, context):
self.context = context # 共享测试上下文
self.logger = context.logger
def execute(self, **kwargs):
"""技能执行入口"""
try:
self._validate_input(kwargs)
return self._execute(**kwargs)
except Exception as e:
self.logger.error(f"Skill 执行失败: {str(e)}")
raise
def _validate_input(self, inputs):
"""参数校验(示例)"""
required = getattr(self, 'REQUIRED_PARAMS', [])
for param in required:
if param not in inputs:
raise ValueError(f"缺少必要参数: {param}")
@abc.abstractmethod
def _execute(self, **kwargs):
"""子类必须实现的执行逻辑"""
pass
原子技能示例:输入文本
class InputTextSkill(BaseSkill):
"""文本输入原子技能"""
REQUIRED_PARAMS = ['locator', 'text']
def _execute(self, locator, text, clear=True):
elem = self.context.driver.find_element(*locator)
if clear:
elem.clear()
elem.send_keys(text)
self.logger.info(f"在 {locator} 输入文本: {text}")
业务技能示例:用户登录
class LoginSkill(BaseSkill):
"""组合原子技能实现业务功能"""
REQUIRED_PARAMS = ['username', 'password']
def _execute(self, username, password):
InputTextSkill(self.context).execute(locator=(By.ID, 'username'),
text=username
)
InputTextSkill(self.context).execute(locator=(By.ID, 'password'),
text=password
)
ClickSkill(self.context).execute(locator=(By.XPATH, '//button[@type="submit"]')
)
# 登录结果验证
AssertSkill(self.context).execute(
condition=lambda: '欢迎页' in self.context.driver.title,
message="登录后未跳转到欢迎页"
)
测试场景构建
def test_checkout_flow(context):
"""组合业务技能完成完整测试"""
LoginSkill(context).execute(
username='test_user',
password='123456'
)
SearchSkill(context).execute(keyword='iPhone')
AddCartSkill(context).execute(sku='MBP2023')
CheckoutSkill(context).execute()
代码规范与避坑指南
PEP8 合规要点
- 类名使用大驼峰,方法名使用小写加下划线
- 每行不超过 79 字符,方法不超过 20 行
- 导入分组(标准库、第三方、本地)用空行分隔
技能粒度平衡原则
- 原子技能:对应最小可测试操作(如点击、输入)
- 业务技能:对应完整业务动作(如登录、下单)
- 避免技能过大(超过 100 行代码)或过小(几个简单操作)
线程安全实践
# 使用线程局部存储管理上下文
import threading
class TestContext:
_local = threading.local()
@classmethod
def current(cls):
if not hasattr(cls._local, 'context'):
cls._local.context = cls()
return cls._local.context
CI/CD 集成建议
- 将技能库作为独立 Python 包管理
- 版本号遵循语义化版本控制(SemVer)
- 自动化测试验证技能兼容性
延伸思考
当技能接口需要升级时,如何设计版本兼容机制?可能的方案:
- 维护多版本技能实现
- 通过适配器模式转换接口
- 自动化迁移工具更新用例
实际项目中,您会如何选择?欢迎在评论区分享您的实践经验。
正文完
