Python 装饰器(Decorator)是 Python 中非常强大且优雅的特性。结合您正在准备的测试开发岗位,装饰器在实际工作中应用极广,比如接口性能耗时统计、失败自动重试、测试用例的前置/后置处理等。
pytest fixture从入门到精通_python fixture-CSDN博客
Python3之从递归到闭包再到装饰器_pow2(5)-CSDN博客
为了让您彻底掌握,我将从底层原理到实战用法,为您进行详细拆解。
一、 核心概念:什么是装饰器?
简单来说,装饰器就是一个函数 。它的作用是:在不修改原函数代码的前提下,为原函数添加额外的功能。
它的底层原理依赖于 Python 的两个特性:
- 函数是一等公民:函数可以像变量一样被传递、赋值。
- 闭包(Closure):内部函数可以访问外部函数的变量。
二、 基础用法:无参装饰器
假设我们有一个普通的测试函数,现在想在不修改它的前提下,记录它的执行时间。
import time
# 1. 定义装饰器函数
def timing_decorator(func):
# 2. 定义内部包装函数(接收原函数的参数)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs) # 执行原函数
end_time = time.time()
print(f"[耗时统计] {func.__name__} 执行耗时: {end_time - start_time:.4f}秒")
return result # 返回原函数的结果
return wrapper
# 3. 使用 @ 语法糖应用装饰器
@timing_decorator
def run_api_test():
time.sleep(1)
print("API 接口测试执行完毕...")
run_api_test()
注:@timing_decorator 本质上等价于 run_api_test = timing_decorator(run_api_test)。
三、 进阶用法:带参数的装饰器
在实际开发中,我们往往需要给装饰器本身传递参数。比如,控制日志是否打印,或者指定重试次数。
实现原理:需要三层嵌套函数。最外层接收装饰器参数,中间层接收被装饰的函数,最内层是包装函数。
def retry(max_attempts=3):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"[重试机制] {func.__name__} 第 {attempt} 次失败: {e}")
if attempt == max_attempts:
raise
return wrapper
return decorator
# 失败最多重试 3 次
@retry(max_attempts=3)
def unstable_network_request():
print("正在请求不稳定的网络接口...")
raise ConnectionError("网络超时")
# unstable_network_request()
四、 高阶用法:保留原函数元信息(必学)
使用装饰器后,原函数的 __name__ 和 __doc__ 会被替换为内部包装函数 wrapper 的信息。这在测试框架中会导致严重问题(比如测试用例名称丢失)。
解决方案 :使用 functools.wraps。
import functools
def my_decorator(func):
@functools.wraps(func) # 关键:保留原函数的元数据
def wrapper(*args, **kwargs):
"""这是 wrapper 的文档字符串"""
return func(*args, **kwargs)
return wrapper
@my_decorator
def my_test_func():
"""这是原始测试函数的文档字符串"""
pass
print(my_test_func.__name__) # 输出: my_test_func (而不是 wrapper)
print(my_test_func.__doc__) # 输出: 这是原始测试函数的文档字符串
五、 测试开发实战场景建议
结合您的岗位需求,您可以将装饰器应用于以下场景:
- UI 自动化测试 :封装元素等待装饰器(
@wait_for_element_visible),替代冗长的WebDriverWait代码。 - 接口自动化测试 :封装鉴权装饰器(
@login_required),在发起请求前自动获取并注入 Token。 - 数据清理 :封装数据库/缓存清理装饰器(
@clean_test_data),在测试用例执行后自动清理脏数据。 - 异常捕获与截图:在 UI 测试失败时,自动捕获异常并调用截图方法保存现场。
pytest fixtures
- fixture用途
- fixture主要用来做初始化环境以及测试结束后的数据清除。
- pytest fixture与setup,teardown功能一样,但比之更加灵活,完全可以代替setup,teardown。
2 fixture参数详解@pytest.fixture(scope='function',params=None,autouse=False,ids=None,name=None)
yield
fixture装饰器,相当于setup,测试用例的前置
* scope: 有四个级别参数'function(默认)'、'class'、'module'、'session'。
* params:一个可选的参数列表,列表中每个数据都可以作为用例的输入。也就说有多少数据,就会形成多少用例。可以通过request.param来获取该次调用的参数。
* autouse:如果True,自动调用fixture功能。如果为False则需要调用fixture。
* ids:每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成。
* name:fixture的名称。这默认为装饰函数的名称。如果fixture在定义它的统一模块。
yield:这个关键字之后的代码相当于teardown,测试用例的后置。
3 fixture的作用范围fixture里面有个scope参数可以控制fixture的作用范围:session>module>class>function
-function:每一个函数或方法都会调用
-class:每一个类调用一次,一个类中可以有多个方法
-module:每一个.py文件调用一次,该文件内又有多个function和class
-session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
4 调用fixture的三种方法
方式1:函数或类里直接传fixture的函数名称
@pytest.fixture()
def test_fixture(self):
print('\n用例开始执行fix1\n')
yield
print('\n用例执行结束fix1\n')
def test_a(self, test_fixture):
print('runing')
assert 1 == 1
执行结果:

方式2: 使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例(可以叠加使用多个装饰器)
@pytest.fixture()
def test_fixture(self):
print('\n用例开始执行fix1\n')
yield
print('\n用例执行结束fix1\n')
@pytest.fixture()
def test_fixture2(self):
print('\n用例开始执行fix2\n')
yield
print('\n用例执行结束fix2\n')
@pytest.mark.usefixtures('test_fixture')
@pytest.mark.usefixtures('test_fixture2')
def test_b(self):
assert 1==1
执行结果
