360 lines
9.7 KiB
Python
360 lines
9.7 KiB
Python
"""
|
|
性能测试 - Locust文件
|
|
|
|
测试内容:
|
|
- 并发用户测试
|
|
- 接口响应时间
|
|
- 吞吐量测试
|
|
- 负载测试
|
|
- 压力测试
|
|
"""
|
|
|
|
from locust import HttpUser, task, between, events
|
|
from locust.runners import MasterRunner
|
|
import time
|
|
import random
|
|
|
|
|
|
# 测试数据
|
|
TEST_USERS = [
|
|
{"username": "admin", "password": "Admin123"},
|
|
{"username": "user1", "password": "Test123"},
|
|
{"username": "user2", "password": "Test123"},
|
|
]
|
|
|
|
ASSET_NAMES = ["联想台式机", "戴尔笔记本", "惠普打印机", "苹果显示器", "罗技鼠标"]
|
|
DEVICE_TYPES = [1, 2, 3, 4, 5]
|
|
ORGANIZATIONS = [1, 2, 3, 4, 5]
|
|
|
|
|
|
class AssetManagementUser(HttpUser):
|
|
"""
|
|
资产管理系统用户模拟
|
|
|
|
模拟真实用户的行为模式
|
|
"""
|
|
|
|
# 等待时间: 用户操作之间间隔1-3秒
|
|
wait_time = between(1, 3)
|
|
|
|
def on_start(self):
|
|
"""用户登录时执行"""
|
|
self.login()
|
|
self.token = None
|
|
self.headers = {}
|
|
|
|
def login(self):
|
|
"""登录获取token"""
|
|
user = random.choice(TEST_USERS)
|
|
|
|
# 先获取验证码
|
|
captcha_resp = self.client.get("/api/v1/auth/captcha")
|
|
if captcha_resp.status_code == 200:
|
|
captcha_data = captcha_resp.json()
|
|
captcha_key = captcha_data["data"]["captcha_key"]
|
|
|
|
# 登录
|
|
login_resp = self.client.post(
|
|
"/api/v1/auth/login",
|
|
json={
|
|
"username": user["username"],
|
|
"password": user["password"],
|
|
"captcha": "1234", # 测试环境固定验证码
|
|
"captcha_key": captcha_key
|
|
}
|
|
)
|
|
|
|
if login_resp.status_code == 200:
|
|
self.token = login_resp.json()["data"]["access_token"]
|
|
self.headers = {
|
|
"Authorization": f"Bearer {self.token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
@task(10)
|
|
def view_asset_list(self):
|
|
"""查看资产列表 (高频操作)"""
|
|
self.client.get(
|
|
"/api/v1/assets",
|
|
headers=self.headers,
|
|
params={
|
|
"page": random.randint(1, 5),
|
|
"page_size": 20
|
|
}
|
|
)
|
|
|
|
@task(5)
|
|
def search_assets(self):
|
|
"""搜索资产 (中频操作)"""
|
|
keywords = ["联想", "戴尔", "台式机", "笔记本", "打印机"]
|
|
keyword = random.choice(keywords)
|
|
|
|
self.client.get(
|
|
"/api/v1/assets",
|
|
headers=self.headers,
|
|
params={"keyword": keyword}
|
|
)
|
|
|
|
@task(3)
|
|
def view_asset_detail(self):
|
|
"""查看资产详情 (中频操作)"""
|
|
asset_id = random.randint(1, 100)
|
|
self.client.get(
|
|
f"/api/v1/assets/{asset_id}",
|
|
headers=self.headers
|
|
)
|
|
|
|
@task(2)
|
|
def view_statistics(self):
|
|
"""查看统计数据 (低频操作)"""
|
|
self.client.get(
|
|
"/api/v1/statistics/overview",
|
|
headers=self.headers
|
|
)
|
|
|
|
@task(1)
|
|
def create_asset(self):
|
|
"""创建资产 (低频操作)"""
|
|
asset_data = {
|
|
"asset_name": f"{random.choice(ASSET_NAMES)}-{int(time.time())}",
|
|
"device_type_id": random.choice(DEVICE_TYPES),
|
|
"organization_id": random.choice(ORGANIZATIONS),
|
|
"model": f"测试型号-{int(time.time())}",
|
|
"serial_number": f"SN-{int(time.time())}",
|
|
"location": f"测试位置-{random.randint(1, 10)}"
|
|
}
|
|
|
|
self.client.post(
|
|
"/api/v1/assets",
|
|
headers=self.headers,
|
|
json=asset_data
|
|
)
|
|
|
|
@task(1)
|
|
def filter_assets(self):
|
|
"""筛选资产 (低频操作)"""
|
|
statuses = ["in_stock", "in_use", "maintenance", "scrapped"]
|
|
status = random.choice(statuses)
|
|
|
|
self.client.get(
|
|
"/api/v1/assets",
|
|
headers=self.headers,
|
|
params={"status": status}
|
|
)
|
|
|
|
|
|
class AssetManagementUserRead(AssetManagementUser):
|
|
"""
|
|
只读用户
|
|
只执行查询操作,不执行写操作
|
|
"""
|
|
|
|
@task(10)
|
|
def view_asset_list(self):
|
|
"""查看资产列表"""
|
|
self.client.get(
|
|
"/api/v1/assets",
|
|
headers=self.headers,
|
|
params={"page": random.randint(1, 10), "page_size": 20}
|
|
)
|
|
|
|
@task(5)
|
|
def view_asset_detail(self):
|
|
"""查看资产详情"""
|
|
asset_id = random.randint(1, 100)
|
|
self.client.get(
|
|
f"/api/v1/assets/{asset_id}",
|
|
headers=self.headers
|
|
)
|
|
|
|
@task(3)
|
|
def search_assets(self):
|
|
"""搜索资产"""
|
|
keywords = ["联想", "戴尔", "惠普"]
|
|
self.client.get(
|
|
"/api/v1/assets",
|
|
headers=self.headers,
|
|
params={"keyword": random.choice(keywords)}
|
|
)
|
|
|
|
@task(2)
|
|
def view_statistics(self):
|
|
"""查看统计数据"""
|
|
self.client.get(
|
|
"/api/v1/statistics/overview",
|
|
headers=self.headers
|
|
)
|
|
|
|
|
|
# 自定义事件处理器
|
|
@events.request.add_listener
|
|
def on_request(request_type, name, response_time, response_length, **kwargs):
|
|
"""
|
|
请求事件监听器
|
|
记录慢请求
|
|
"""
|
|
if response_time > 1000: # 响应时间超过1秒
|
|
print(f"慢请求警告: {name} 耗时 {response_time}ms")
|
|
|
|
|
|
@events.test_stop.add_listener
|
|
def on_test_stop(environment, **kwargs):
|
|
"""
|
|
测试结束事件
|
|
输出测试统计
|
|
"""
|
|
if not isinstance(environment.runner, MasterRunner):
|
|
print("\n" + "="*50)
|
|
print("性能测试完成")
|
|
print("="*50)
|
|
|
|
stats = environment.stats
|
|
print(f"\n总请求数: {stats.total.num_requests}")
|
|
print(f"失败请求数: {stats.total.num_failures}")
|
|
print(f"平均响应时间: {stats.total.avg_response_time}ms")
|
|
print(f"中位数响应时间: {stats.total.median_response_time}ms")
|
|
print(f"95%请求响应时间: {stats.total.get_response_time_percentile(0.95)}ms")
|
|
print(f"99%请求响应时间: {stats.total.get_response_time_percentile(0.99)}ms")
|
|
print(f"请求/秒 (RPS): {stats.total.total_rps}")
|
|
print(f"失败率: {stats.total.fail_ratio * 100:.2f}%")
|
|
|
|
# 性能指标评估
|
|
print("\n性能评估:")
|
|
avg_response = stats.total.avg_response_time
|
|
if avg_response < 200:
|
|
print("✓ 响应时间: 优秀 (< 200ms)")
|
|
elif avg_response < 500:
|
|
print("✓ 响应时间: 良好 (< 500ms)")
|
|
elif avg_response < 1000:
|
|
print("⚠ 响应时间: 一般 (< 1000ms)")
|
|
else:
|
|
print("✗ 响应时间: 差 (> 1000ms)")
|
|
|
|
rps = stats.total.total_rps
|
|
if rps > 100:
|
|
print("✓ 吞吐量: 优秀 (> 100 RPS)")
|
|
elif rps > 50:
|
|
print("✓ 吞吐量: 良好 (> 50 RPS)")
|
|
elif rps > 20:
|
|
print("⚠ 吞吐量: 一般 (> 20 RPS)")
|
|
else:
|
|
print("✗ 吞吐量: 差 (< 20 RPS)")
|
|
|
|
fail_ratio = stats.total.fail_ratio * 100
|
|
if fail_ratio < 1:
|
|
print("✓ 失败率: 优秀 (< 1%)")
|
|
elif fail_ratio < 5:
|
|
print("✓ 失败率: 良好 (< 5%)")
|
|
else:
|
|
print("✗ 失败率: 差 (> 5%)")
|
|
|
|
print("="*50 + "\n")
|
|
|
|
|
|
# 性能测试目标
|
|
PERFORMANCE_TARGETS = {
|
|
"avg_response_time": 500, # 平均响应时间 < 500ms
|
|
"p95_response_time": 1000, # 95%响应时间 < 1000ms
|
|
"rps": 50, # 吞吐量 > 50 RPS
|
|
"fail_ratio": 0.01 # 失败率 < 1%
|
|
}
|
|
|
|
|
|
class PerformanceTestRunner:
|
|
"""
|
|
性能测试运行器
|
|
提供不同场景的性能测试
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.scenarios = {
|
|
"smoke": self.smoke_test,
|
|
"normal": self.normal_load_test,
|
|
"stress": self.stress_test,
|
|
"spike": self.spike_test,
|
|
"endurance": self.endurance_test
|
|
}
|
|
|
|
def smoke_test(self):
|
|
"""
|
|
冒烟测试
|
|
少量用户,验证系统基本功能
|
|
"""
|
|
return {
|
|
"num_users": 10,
|
|
"spawn_rate": 2,
|
|
"run_time": "1m"
|
|
}
|
|
|
|
def normal_load_test(self):
|
|
"""
|
|
正常负载测试
|
|
模拟日常使用情况
|
|
"""
|
|
return {
|
|
"num_users": 50,
|
|
"spawn_rate": 5,
|
|
"run_time": "5m"
|
|
}
|
|
|
|
def stress_test(self):
|
|
"""
|
|
压力测试
|
|
逐步增加用户直到系统达到极限
|
|
"""
|
|
return {
|
|
"num_users": 200,
|
|
"spawn_rate": 10,
|
|
"run_time": "10m"
|
|
}
|
|
|
|
def spike_test(self):
|
|
"""
|
|
尖峰测试
|
|
突然大量用户访问
|
|
"""
|
|
return {
|
|
"num_users": 500,
|
|
"spawn_rate": 50,
|
|
"run_time": "2m"
|
|
}
|
|
|
|
def endurance_test(self):
|
|
"""
|
|
耐力测试
|
|
长时间稳定负载
|
|
"""
|
|
return {
|
|
"num_users": 100,
|
|
"spawn_rate": 10,
|
|
"run_time": "30m"
|
|
}
|
|
|
|
|
|
# 使用说明
|
|
"""
|
|
运行性能测试:
|
|
|
|
1. 冒烟测试 (10用户, 1分钟):
|
|
locust -f locustfile.py --headless -u 10 -r 2 -t 1m
|
|
|
|
2. 正常负载测试 (50用户, 5分钟):
|
|
locust -f locustfile.py --headless -u 50 -r 5 -t 5m
|
|
|
|
3. 压力测试 (200用户, 10分钟):
|
|
locust -f locustfile.py --headless -u 200 -r 10 -t 10m
|
|
|
|
4. 尖峰测试 (500用户, 2分钟):
|
|
locust -f locustfile.py --headless -u 500 -r 50 -t 2m
|
|
|
|
5. Web界面模式:
|
|
locust -f locustfile.py --host=http://localhost:8000
|
|
然后访问 http://localhost:8089
|
|
|
|
6. 分布式测试 (Master):
|
|
locust -f locustfile.py --master --expect-workers=4
|
|
|
|
7. 分布式测试 (Worker):
|
|
locust -f locustfile.py --worker --master-host=<master-ip>
|
|
"""
|