Master Decorator Factories: Logging, Retry, Permissions & More for API Testing
This article explores how decorator factories can enhance API automation testing by providing reusable solutions for logging, automatic retries, permission checks, performance monitoring, data-driven testing, and environment switching, complete with Python code examples and usage demonstrations.
Decorator factories are widely used in API automation testing frameworks to improve code readability, reusability, and to implement cross‑cutting concerns such as logging, retries, permission checks, performance monitoring, data‑driven testing, and environment switching.
1. Logging decorator factory (supports different levels)
Creates a decorator that logs API calls at the specified level (info or debug).
def make_logger(level='info', enabled=True):
def logger_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not enabled:
return func(*args, **kwargs)
log_method = {'info': logging.info, 'debug': logging.debug}.get(level.lower(), logging.info)
log_method(f"[{func.__name__}] 正在调用接口...")
result = func(*args, **kwargs)
log_method(f"[{func.__name__}] 接口调用完成")
return result
return wrapper
return logger_decoratorUsage example:
@make_logger('debug')
def get_user_info(user_id):
print(f"获取用户 {user_id} 的信息")
get_user_info(1001)Sample output demonstrates debug logs before and after the function execution.
2. Automatic retry decorator factory
Encapsulates a generic retry logic that automatically retries a failing operation up to N times, suitable for unstable network requests.
def retry(max_retries=3, delay=1, exceptions=(Exception,)):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
retries = 0
while retries <= max_retries:
try:
return func(*args, **kwargs)
except exceptions as e:
retries += 1
logging.warning(f"[{func.__name__}] 第 {retries}/{max_retries} 次重试失败: {e}")
if retries > max_retries:
raise
time.sleep(delay)
return None
return wrapper
return decoratorUsage example:
@retry(max_retries=3, exceptions=(requests.exceptions.RequestException,))
def fetch_data(url):
response = requests.get(url)
return response.json()
try:
data = fetch_data("https://api.example.com/data")
print(data)
except Exception as e:
print("最终请求失败:", e)3. Permission verification decorator factory (supports role control)
Determines whether a user with a specific role (e.g., admin, user) is allowed to execute an API function.
def permission_required(role='user'):
def decorator(func):
@wraps(func)
def wrapper(context, *args, **kwargs):
user_role = context.get('role')
if user_role != role:
logging.error(f"[{func.__name__}] 权限不足,需要 {role} 角色,当前为 {user_role}")
raise PermissionError(f"权限不足,需要 {role} 角色")
return func(context, *args, **kwargs)
return wrapper
return decoratorUsage example:
@permission_required('admin')
def delete_user(context, user_id):
print(f"管理员 {context['username']} 删除了用户 {user_id}")
context = {'username': '张三', 'role': 'admin'}
delete_user(context, 1002)4. Performance monitoring decorator factory
Records the execution time of an API function for performance analysis or optimization.
import time
def performance_monitor(enabled=True):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not enabled:
return func(*args, **kwargs)
start = time.time()
result = func(*args, **kwargs)
duration = time.time() - start
logging.info(f"[{func.__name__}] 执行耗时: {duration:.4f} 秒")
return result
return wrapper
return decoratorUsage example:
@performance_monitor()
def slow_api():
time.sleep(1.2)
slow_api()5. Data‑driven decorator factory (parameterized testing)
Dynamically injects test data into an API function to achieve parameterized testing.
def data_provider(data_list):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for data in data_list:
logging.info(f"运行测试用例: {data}")
func(*data)
return wrapper
return decoratorUsage example:
@test_data([
("张三", "北京"),
("李四", "上海"),
("王五", "广州")
])
def test_user_location(name, city):
print(f"{name} 的城市是 {city}")
test_user_location()6. Environment switch decorator factory (dev/test/prod)
Automatically switches API endpoints or behavior based on the configured environment.
def environment_switch(env='dev'):
base_urls = {
'dev': 'http://dev.api.example.com',
'test': 'http://test.api.example.com',
'prod': 'http://api.example.com'
}
def decorator(func):
@wraps(func)
def wrapper(endpoint, *args, **kwargs):
full_url = f"{base_urls[env]}/{endpoint}"
logging.info(f"[{func.__name__}] 请求地址: {full_url}")
return func(full_url, *args, **kwargs)
return wrapper
return decoratorUsage example:
@environment_switch('test')
def call_api(url):
print(f"正在调用接口: {url}")
call_api("users/list")Sample output shows the constructed request URL and the call message.
Summary table comparing the six decorator factories:
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
