做自动化测试久了,你是否发现自己经常在写重复的代码?
比如,每个测试用例执行前都要打印日志:“开始执行用例A...”,执行完要打印:“用例A执行结束”; 又比如,遇到网络波动导致报错,你不得不给很多不稳定的接口加上 try...except 进行重试。
这些与业务逻辑无关,但又必须写的“样板代码”,不仅让脚本变得臃肿,还难以维护。
今天我们要介绍的 Python 黑科技——装饰器 (Decorator),就是为了解决这个问题而生的。它就像给你的函数穿上了一层“钢铁侠战衣”,不改变函数本身,却能赋予它强大的超能力。
01. 什么是装饰器?(小白也能懂)
用通俗的话说,装饰器就是一个**“包装袋”**。
假设你写了一个函数 test_login(),它的核心功能是“登录”。 现在老板要求:所有测试用例执行前,必须检查数据库连接状态。
原理公式:
装饰器 = 高阶函数 + 闭包它本质上是一个函数,它接收一个函数作为参数,并返回一个新的函数。
02. 最基础的语法模板
在实战之前,我们先看一个最简单的装饰器长什么样。请把这个模板刻在脑子里,后面所有的骚操作都是基于此扩展的:
# 这是一个装饰器函数def my_decorator(func): def wrapper(): print(">>> 1. 在函数执行前做点什么...") func() # 执行真正的函数 print(">>> 2. 在函数执行后做点什么...") return wrapper# 使用装饰器@my_decoratordef test_case(): print("正在执行核心测试步骤...")# 运行test_case()
运行结果:
>>> 1. 在函数执行前做点什么...正在执行核心测试步骤...>>> 2. 在函数执行后做点什么...
你看,我们没有改动 test_case 内部的一行代码,就凭空给它增加了前置和后置的功能!03. 实战场景一:自动添加测试日志
这是测试框架中最常用的功能。我们希望每个用例执行时,自动打印函数名和说明,方便在 Jenkins 或报告中排查问题。
代码实现:
import functoolsdef log_case_info(func): # 使用 functools.wraps 能够保留原函数的名称和注释信息,这在测试框架中很重要 @functools.wraps(func) def wrapper(*args, **kwargs): print(f"\n{'='*20}") print(f"开始执行用例: {func.__name__}") print(f"用例描述: {func.__doc__}") print(f"{'='*20}") # 执行原本的测试函数,并获取返回值 result = func(*args, **kwargs) print(f"用例 {func.__name__} 执行完毕\n") return result return wrapper# --- 使用方式 ---@log_case_infodef test_add_cart(item_name): """验证添加购物车功能""" print(f"正在将 {item_name} 添加到购物车...") return True# --- 运行 ---test_add_cart("iPhone 15")
====================开始执行用例: test_add_cart用例描述: 验证添加购物车功能====================正在将 iPhone 15 添加到购物车...用例 test_add_cart 执行完毕
这里的 *args 和 **kwargs 是关键,它保证了无论你的测试用例接收什么参数,装饰器都能完美兼容。04. 实战场景二:不稳定的用例自动重试
UI 自动化测试中,经常因为页面渲染慢报错;接口测试中,偶尔会遇到网络抖动。如果因为这 1% 的网络问题导致整个构建失败,非常冤枉。
我们可以写一个**“复活甲”装饰器**,当用例报错时,自动重试指定次数。
代码实现:
import timedef retry(max_times=3, delay=1): """ 带参数的装饰器:可以指定重试次数和等待时间 """ def decorator(func): def wrapper(*args, **kwargs): for i in range(max_times): try: return func(*args, **kwargs) # 尝试执行 except Exception as e: print(f"出现错误: {e},正在进行第 {i+1}/{max_times} 次重试...") time.sleep(delay) # 如果循环结束还没成功,抛出最后的异常 raise Exception(f"重试 {max_times} 次后依然失败") return wrapper return decorator# --- 使用方式 ---import random@retry(max_times=3, delay=2)def test_unstable_api(): if random.randint(1, 10) > 2: # 模拟80%的概率失败 raise ConnectionError("网络超时") print("API 请求成功!")# --- 运行 ---# 你会看到控制台自动打印重试日志,直到成功或次数用尽test_unstable_api()
出现错误: 网络超时,正在进行第 1/3 次重试...出现错误: 网络超时,正在进行第 2/3 次重试...出现错误: 网络超时,正在进行第 3/3 次重试...Traceback (most recent call last): File "E:\b.py", line 32, in <module> test_unstable_api() ~~~~~~~~~~~~~~~~~^^ File "E:\b.py", line 16, in wrapper raise Exception(f"重试 {max_times} 次后依然失败")Exception: 重试 3 次后依然失败
这个装饰器比上一个更高级,它是**“带参数的装饰器”**。由于我们需要传入 max_times,所以需要在最外层多包裹一层函数。05. 实战场景三:权限校验与前置准备
有些测试用例需要管理员权限才能运行,或者需要先获取 token。我们可以把这些逻辑抽离出来。
代码实现:
# 模拟当前的登录状态#current_user_role = "guest" #使用此权限无法成功调用测试方法current_user_role = "admin" #使用此权限可以成功调用测试方法def require_admin(func): def wrapper(*args, **kwargs): if current_user_role != "admin": print(f"[跳过] 用例 {func.__name__} 需要管理员权限,当前是 {current_user_role}") return None # 或者抛出 SkipTest 异常 return func(*args, **kwargs) return wrapper# --- 使用方式 ---@require_admindef test_delete_user(): print("正在删除用户数据(危险操作)...")@require_admindef test_view_report(): print("正在查看管理员报表...")# --- 运行 ---test_delete_user() # 因为是 guest,这行不会执行任何危险操作
总结
Python 装饰器在自动化测试中的应用远不止于此,它还能用来做:
测试计时(性能分析)
依赖注入(自动把浏览器 Driver 传给用例)
数据驱动(解析 Excel 数据喂给函数)
记住一句话: 当你发现自己在很多个测试函数里写同样的非业务代码时,就是使用装饰器的时候了。
想系统学习测试开发的朋友,可以添加吴老师微信:wulaoshi1978现在可以1对1学习指定内容,效率高,见效快,学了都说好!