2. 压测脚本设计原则:可维护性、可复用性、参数化、数据驱动设计思想
压测脚本不仅是流量模拟的载体,更是团队协作与长期巡检的资产。若脚本设计缺乏章法,随着业务迭代,脚本将迅速腐化,导致维护成本飙升、压测结果失真。本章围绕四大核心设计原则展开,帮助构建健壮、可演进的脚本体系。
2.1 四大设计原则概览
| 原则 | 核心目标 | 关键实践 | 反例 |
|---|---|---|---|
| 可维护性 | 降低脚本修改成本与出错概率 | 模块化、注释规范、统一日志 | 单文件数千行、无注释、魔法数字 |
| 可复用性 | 一次编写,多场景调用 | 函数封装、基类继承、组件化 | 重复代码复制粘贴、硬编码依赖 |
| 参数化 | 分离脚本逻辑与运行时配置 | 配置文件、环境变量、命令行参数 | URL/账号/阈值写在代码里 |
| 数据驱动 | 用数据控制测试逻辑与断言 | CSV/JSON 数据源、循环迭代 | 每个用例写一个独立脚本 |
设计优先级建议: 在团队初期,优先落地参数化与可维护性;当脚本数量超过 20 个时,必须引入可复用性与数据驱动机制。
2.2 可维护性:让脚本成为可读的文档
可维护性决定了脚本的生命周期。一个可维护的压测脚本应具备以下特征:
- 模块化拆分: 将登录、下单、支付等业务步骤拆分为独立函数或类文件。
- 统一日志规范: 使用结构化日志(如 JSON 格式),包含时间戳、线程号、请求耗时、响应状态码。
- 异常处理与重试: 对网络抖动、服务限流等场景设计指数退避重试,避免脚本直接崩溃。
- 注释即文档: 在关键逻辑处说明业务意图,而非描述代码行为。
# 反例:不可维护的脚本片段
def run():
r = requests.post('http://api.example.com/login', json={'u': 'test', 'p': '123'})
t = r.json()['']
r2 = requests.get('http://api.example.com/order?page=1', headers={'Authorization': t})
print(r2.status_code)
# 正例:可维护的脚本片段
def login(base_url: str, username: str, password: str) -> str:
"""用户登录,返回 access_"""
url = f"{base_url}/login"
payload = {"username": username, "password": password}
try:
resp = requests.post(url, json=payload, timeout=5)
resp.raise_for_status()
= resp.json()[""]
logger.info(f"Login success for {username}, prefix: {[:8]}")
return
except requests.exceptions.RequestException as e:
logger.error(f"Login failed for {username}: {e}")
raise
2.3 可复用性:构建脚本组件库
可复用性要求脚本具备“搭积木”的能力。通过抽象公共层,减少重复开发,提升压测效率。
复用层次模型: 工具层 → 业务层 → 场景层。工具层封装 HTTP 客户端、数据库连接、加解密等;业务层封装具体业务接口;场景层组合业务层完成复杂流程。
# 工具层:通用 HTTP 客户端
class HttpClient:
def __init__(self, base_url: str, default_headers: dict = None):
self.base_url = base_url
self.session = requests.Session()
if default_headers:
self.session.headers.update(default_headers)
def get(self, path: str, **kwargs):
return self.session.get(f"{self.base_url}{path}", **kwargs)
def post(self, path: str, **kwargs):
return self.session.post(f"{self.base_url}{path}", **kwargs)
# 业务层:继承工具层,封装具体业务
class OrderService(HttpClient):
def create_order(self, user_id: str, product_id: str) -> dict:
payload = {"user_id": user_id, "product_id": product_id}
resp = self.post("/order/create", json=payload)
return resp.json()
def query_order(self, order_id: str) -> dict:
resp = self.get(f"/order/{order_id}")
return resp.json()
2.4 参数化:将配置从代码中剥离
参数化是脚本适应不同环境(开发、测试、预发、生产)的基础。核心思路是:代码不变,配置可变。
| 参数类型 | 推荐方式 | 示例 |
|---|---|---|
| 环境相关 | 环境变量 + .env 文件 | BASE_URL=http://pre.api.com |
| 业务数据 | YAML/JSON 配置文件 | users: [{name: "u1", pwd: "p1"}] |
| 压测参数 | 命令行参数 (argparse) | --threads 50 --duration 300 |
| 动态变量 | 函数返回值注入 | = login() |
# 使用 argparse 实现命令行参数化
import argparse
def parse_args():
parser = argparse.ArgumentParser(description="Performance Test Runner")
parser.add_argument("--env", choices=["dev", "staging", "prod"], default="dev")
parser.add_argument("--threads", type=int, default=10)
parser.add_argument("--duration", type=int, default=60)
parser.add_argument("--data-file", type=str, default="test_data.csv")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
print(f"Running on {args.env} with {args.threads} threads for {args.duration}s")
2.5 数据驱动:用数据编排测试逻辑
数据驱动将测试数据与测试逻辑彻底分离。同一套脚本,通过切换数据源即可覆盖不同场景,极大提升脚本复用率。
常见误区: 数据驱动不是简单地把数据放在 CSV 里,而是要求脚本逻辑能够自动适配数据变化,包括字段增减、断言规则变化、流程分支选择。
# 数据驱动示例:从 CSV 读取用户数据,循环执行登录验证
import csv
def load_test_data(file_path: str) -> list:
with open(file_path, "r") as f:
reader = csv.DictReader(f)
return list(reader)
def run_data_driven_test(data_file: str):
test_cases = load_test_data(data_file)
for case in test_cases:
username = case["username"]
password = case["password"]
expected_code = int(case["expected_code"])
# 执行登录并断言
resp = login(username, password)
assert resp.status_code == expected_code, \
f"Failed for {username}: expected {expected_code}, got {resp.status_code}"
logger.info(f"PASS: {username} - {resp.status_code}")
2.6 设计原则协同关系
四大原则并非孤立存在,而是相互支撑、层层递进。下图展示了它们之间的协同关系:
2.7 实践检查清单
在编写或评审压测脚本时,可对照以下清单进行质量把关:
- 可维护性: 是否遵循单一职责?是否有异常处理?日志是否包含关键上下文?
- 可复用性: 公共方法是否已抽取?是否支持继承或组合?是否存在硬编码依赖?
- 参数化: 环境地址、账号、超时时间是否可配置?是否支持多环境切换?
- 数据驱动: 测试数据是否独立存储?脚本能否自动适配数据字段变化?断言是否基于数据动态生成?
推荐实践: 在项目根目录创建
config/、data/、lib/、scenarios/ 四个目录,分别存放配置、测试数据、公共库和场景脚本,从目录结构上强制落地设计原则。