<think>我们将根据提供的JMeter测试计划XML文件,将其中的测试逻辑转换为Python脚本。主要包含以下步骤:
1. 解析XML文件,提取测试计划中的关键元素(线程组、HTTP请求、头信息等)
2. 使用Python的requests库模拟HTTP请求
3. 实现仅一次控制器(OnceOnlyController)逻辑
4. 处理HTTP信息头管理器(HeaderManager)
5. 处理请求参数
注意:由于JMeter脚本中使用了变量(如${username}),我们在Python中需要先定义这些变量或从外部读取。
由于XML内容较长,我们不会完全解析整个XML,而是根据提供的片段,提取出需要执行的HTTP请求序列,并按照顺序执行。
根据提供的XML片段,我们提取出以下请求(忽略enabled=false的请求):
1. 接口104: GET https://www.fifedu.com/
2. 接口117: POST https://www.fifedu.com/iplat/cms/common/getUserName
3. 接口121: POST https://www.fifedu.com/iplat/bp/common/getAllApplication
4. 接口127: POST https://www.fifedu.com/iplat/html/www/front/contentList (带参数categoryId=2)
另外,注意每个请求都有对应的HTTP头管理器。
我们将按照以下步骤编写Python脚本:
- 创建一个Session对象,保持会话(模拟JMeter中的Cookie管理器)
- 按顺序发送请求
- 为每个请求设置对应的头信息
- 对于POST请求,如果有参数则设置参数
由于JMeter脚本中使用了仅一次控制器,所以这些请求在脚本中只执行一次。
另外,注意JMeter脚本中有一个用户定义的变量,但XML中未定义具体变量,所以我们在脚本中需要自己定义变量(如username),或者从外部读取。
由于接口143被禁用,我们忽略它。
下面我们开始编写Python代码。</think># JMeter 脚本转 Python 实现
下面我将提供的 JMeter 测试计划转换为完整的 Python 脚本,使用 `requests` 库实现所有 HTTP 请求功能,并保留原始 JMeter 脚本的结构和逻辑。
```python
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
import time
import json
from collections import defaultdict
# ====================
# 配置参数
# ====================
BASE_URL = "https://www.fifedu.com"
USER_AGENT = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0"
USERNAME = "test_user" # 替换为实际用户名
PASSWORD = "test_password" # 替换为实际密码
# ====================
# 工具函数
# ====================
def create_session():
"""创建带重试机制的会话"""
session = requests.Session()
# 配置重试策略
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("https://", adapter)
session.mount("http://", adapter)
return session
def log_request(response, name, start_time):
"""记录请求日志"""
latency = int((time.time() - start_time) * 1000) # 毫秒
print(f"\n{'='*50}")
print(f"请求名称: {name}")
print(f"URL: {response.url}")
print(f"状态码: {response.status_code}")
print(f"响应时间: {latency}ms")
print(f"响应大小: {len(response.content)} bytes")
# 打印响应内容前200字符
content_preview = response.text[:200] + ('...' if len(response.text) > 200 else '')
print(f"响应预览: {content_preview}")
def extract_json_value(response, path):
"""从JSON响应中提取值"""
try:
data = response.json()
keys = path.split('.')
for key in keys:
data = data[key]
return data
except (json.JSONDecodeError, KeyError, TypeError):
return None
# ====================
# HTTP 请求函数
# ====================
def request_104_home(session):
"""接口104: 访问首页"""
start_time = time.time()
url = f"{BASE_URL}/"
headers = {
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"Upgrade-Insecure-Requests": "1",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": USER_AGENT,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
}
response = session.get(url, headers=headers)
log_request(response, "接口104: 访问首页", start_time)
# 简单验证
if "欢迎" in response.text:
print("✅ 首页验证成功")
else:
print("❌ 首页验证失败")
return response
def request_117_get_username(session, referer):
"""接口117: 获取用户名"""
start_time = time.time()
url = f"{BASE_URL}/iplat/cms/common/getUserName"
headers = {
"Referer": referer,
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"X-Requested-With": "XMLHttpRequest",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": USER_AGENT,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
response = session.post(url, headers=headers)
log_request(response, "接口117: 获取用户名", start_time)
# 提取用户名
username = extract_json_value(response, "username")
if username:
print(f"✅ 获取到用户名: {username}")
else:
print("❌ 未获取到用户名")
return response, username
def request_121_get_applications(session, referer):
"""接口121: 获取所有应用"""
start_time = time.time()
url = f"{BASE_URL}/iplat/bp/common/getAllApplication"
headers = {
"Referer": referer,
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"X-Requested-With": "XMLHttpRequest",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": USER_AGENT,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
response = session.post(url, headers=headers)
log_request(response, "接口121: 获取所有应用", start_time)
# 检查响应
applications = extract_json_value(response, "applications")
if applications and len(applications) > 0:
print(f"✅ 获取到 {len(applications)} 个应用")
else:
print("❌ 未获取到应用数据")
return response, applications
def request_127_content_list(session, referer):
"""接口127: 获取内容列表"""
start_time = time.time()
url = f"{BASE_URL}/iplat/html/www/front/contentList"
headers = {
"Referer": referer,
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": USER_AGENT,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
data = {
"categoryId": "2"
}
response = session.post(url, headers=headers, data=data)
log_request(response, "接口127: 获取内容列表", start_time)
# 检查响应
content_list = extract_json_value(response, "contentList")
if content_list and len(content_list) > 0:
print(f"✅ 获取到 {len(content_list)} 条内容")
else:
print("❌ 未获取到内容数据")
return response, content_list
def request_143_validate_user(session, referer, username):
"""接口143: 验证用户 (在JMeter中已禁用)"""
start_time = time.time()
url = f"{BASE_URL}/iplat/valid/user"
headers = {
"Referer": referer,
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Accept-Encoding": "gzip, deflate, br",
"User-Agent": USER_AGENT,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
data = {
"username": username
}
response = session.post(url, headers=headers, data=data)
log_request(response, "接口143: 验证用户", start_time)
# 检查响应
is_valid = extract_json_value(response, "valid")
if is_valid:
print(f"✅ 用户 {username} 验证成功")
else:
print(f"❌ 用户 {username} 验证失败")
return response, is_valid
# ====================
# 主测试流程
# ====================
def run_test_plan():
"""执行完整的测试计划"""
print(f"开始执行测试计划,目标站点: {BASE_URL}")
session = create_session()
try:
# 步骤1: 访问首页
home_response = request_104_home(session)
referer_url = home_response.url # 获取首页URL作为后续请求的Referer
# 步骤2: 获取用户名
_, username = request_117_get_username(session, referer_url)
# 步骤3: 获取所有应用
_, applications = request_121_get_applications(session, referer_url)
# 步骤4: 获取内容列表
_, content_list = request_127_content_list(session, referer_url)
# 步骤5: 验证用户 (在JMeter中已禁用)
# if username:
# request_143_validate_user(session, referer_url, username)
print("\n测试计划执行完成!")
# 返回收集的数据
return {
"username": username,
"application_count": len(applications) if applications else 0,
"content_count": len(content_list) if content_list else 0
}
except Exception as e:
print(f"测试执行出错: {str(e)}")
return None
finally:
session.close()
# ====================
# 性能测试扩展 (使用Locust)
# ====================
from locust import HttpUser, task, between, TaskSet
class FIFEduUserBehavior(TaskSet):
"""模拟JMeter线程组行为"""
def on_start(self):
"""初始化操作 (仅一次控制器)"""
self.referer = f"{BASE_URL}/"
self.session = self.client # 使用Locust的内置客户端
# 执行初始化请求
self.request_104_home()
def request_104_home(self):
"""访问首页"""
headers = {
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"Upgrade-Insecure-Requests": "1",
"User-Agent": USER_AGENT,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
}
with self.client.get("/", headers=headers, catch_response=True) as response:
if "欢迎" in response.text:
response.success()
else:
response.failure("首页验证失败")
@task(3)
def request_117_get_username(self):
"""获取用户名 (权重3)"""
headers = {
"Referer": self.referer,
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"X-Requested-With": "XMLHttpRequest",
"User-Agent": USER_AGENT,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
with self.client.post("/iplat/cms/common/getUserName", headers=headers, catch_response=True) as response:
username = extract_json_value(response, "username")
if username:
response.success()
else:
response.failure("未获取到用户名")
@task(2)
def request_121_get_applications(self):
"""获取所有应用 (权重2)"""
headers = {
"Referer": self.referer,
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"X-Requested-With": "XMLHttpRequest",
"User-Agent": USER_AGENT,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
with self.client.post("/iplat/bp/common/getAllApplication", headers=headers, catch_response=True) as response:
applications = extract_json_value(response, "applications")
if applications and len(applications) > 0:
response.success()
else:
response.failure("未获取到应用数据")
@task(1)
def request_127_content_list(self):
"""获取内容列表 (权重1)"""
headers = {
"Referer": self.referer,
"Accept-Language": "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3",
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"User-Agent": USER_AGENT,
"Accept": "application/json, text/javascript, */*; q=0.01"
}
data = {"categoryId": "2"}
with self.client.post("/iplat/html/www/front/contentList", headers=headers, data=data, catch_response=True) as response:
content_list = extract_json_value(response, "contentList")
if content_list and len(content_list) > 0:
response.success()
else:
response.failure("未获取到内容数据")
class FIFEduUser(HttpUser):
"""模拟JMeter线程组配置"""
tasks = [FIFEduUserBehavior]
wait_time = between(1, 5) # 随机等待时间
host = BASE_URL
# ====================
# 执行测试
# ====================
if __name__ == "__main__":
print("="*60)
print("执行功能测试")
print("="*60)
test_results = run_test_plan()
if test_results:
print("\n测试结果摘要:")
print(f"用户名: {test_results['username']}")
print(f"应用数量: {test_results['application_count']}")
print(f"内容数量: {test_results['content_count']}")
print("\n提示: 要运行性能测试,请使用以下命令:")
print("locust -f this_script.py")
```
## 代码解释
### 1. 核心组件实现
- **会话管理**:使用 `requests.Session()` 保持会话状态和 cookies
- **重试机制**:配置了自动重试策略处理网络问题
- **请求日志**:详细记录每个请求的 URL、状态码、响应时间和响应大小
- **JSON 提取**:实现类似 JMeter 的 JSON 提取器功能
- **验证逻辑**:对每个响应进行基本验证
### 2. 测试流程实现
1. **访问首页** (`/`): 模拟用户首次访问
2. **获取用户名** (`/iplat/cms/common/getUserName`): 获取当前登录用户名
3. **获取应用列表** (`/iplat/bp/common/getAllApplication`): 获取系统应用
4. **获取内容列表** (`/iplat/html/www/front/contentList`): 带参数请求内容数据
5. **用户验证** (已禁用): 在 JMeter 中禁用的功能,Python 中也注释掉
### 3. Locust 性能测试扩展
- 实现了完整的性能测试类 `FIFEduUser`
- 使用 `@task` 权重控制不同请求的执行频率
- 支持分布式测试和实时监控
### 4. 关键特性
- **自动重试**:对失败请求自动重试最多 3 次
- **Referer 处理**:自动传递前一个请求的 URL 作为 Referer
- **响应验证**:对每个响应进行内容验证
- **详细日志**:记录每个请求的关键指标
## 使用说明
### 1. 功能测试
直接运行脚本:
```bash
python fifedu_test.py
```
### 2. 性能测试
安装依赖后运行 Locust:
```bash
pip install locust
locust -f fifedu_test.py
```
访问 `http://localhost:8089` 启动测试
### 3. 配置参数
在脚本开头修改:
```python
BASE_URL = "https://www.fifedu.com" # 目标站点
USER_AGENT = "..." # 自定义User-Agent
USERNAME = "your_username" # 实际用户名
PASSWORD = "your_password" # 实际密码
```
## 与原 JMeter 脚本的对应关系
| JMeter 组件 | Python 实现 |
|----------------------|---------------------------------|
| 线程组 | `run_test_plan()` 函数 |
| HTTP 请求采样器 | `request_xxx()` 系列函数 |
| HTTP 信息头管理器 | 每个请求的 headers 字典 |
| 仅一次控制器 | `on_start()` 方法 (Locust部分) |
| 循环控制器 | `@task` 装饰器 (Locust部分) |
| 用户定义的变量 | 脚本开头的配置参数 |
| 响应断言 | 每个请求后的验证逻辑 |
## 扩展建议
1. **添加参数化**:从 CSV 文件读取测试数据
2. **集成 CI/CD**:将测试集成到 Jenkins 或 GitLab CI
3. **生成报告**:使用 Locust 的 HTML 报告或生成自定义报告
4. **添加监控**:集成 Prometheus 监控测试指标