Files
novalon-manage-system/api_integration_tests/tests/test_performance.py
T
张翔 c50ccd258f feat: 重构测试框架并优化代码结构
refactor(tests): 将e2e_tests迁移到tests_suite和api_integration_tests
style: 为Java类添加文档注释
docs: 更新.gitignore和配置文件
test: 添加性能测试和Playwright测试脚本
chore: 清理旧测试文件和配置
2026-03-14 13:49:39 +08:00

200 lines
7.7 KiB
Python

"""
性能测试基础框架
"""
import pytest
import time
import asyncio
import statistics
from typing import List, Dict, Any
from httpx import AsyncClient
from loguru import logger
@pytest.mark.performance
@pytest.mark.slow
class PerformanceTest:
"""性能测试基类"""
@pytest.fixture
async def perf_client(self, authenticated_client: AsyncClient) -> AsyncClient:
"""性能测试客户端"""
return authenticated_client
@pytest.fixture
def performance_thresholds(self):
"""性能阈值配置"""
return {
"response_time_p95": 2000, # 95%的请求响应时间应小于2秒
"response_time_p99": 5000, # 99%的请求响应时间应小于5秒
"error_rate": 0.05, # 错误率应小于5%
"throughput_min": 10, # 最小吞吐量(请求/秒)
}
async def measure_request_time(self, client: AsyncClient, method: str,
url: str, **kwargs) -> float:
"""测量单个请求时间"""
start_time = time.time()
if method.upper() == "GET":
response = await client.get(url, **kwargs)
elif method.upper() == "POST":
response = await client.post(url, **kwargs)
elif method.upper() == "PUT":
response = await client.put(url, **kwargs)
elif method.upper() == "DELETE":
response = await client.delete(url, **kwargs)
else:
raise ValueError(f"Unsupported method: {method}")
end_time = time.time()
response_time = (end_time - start_time) * 1000 # 转换为毫秒
return response_time
async def measure_concurrent_requests(self, client: AsyncClient, method: str,
url: str, concurrency: int = 10,
**kwargs) -> Dict[str, Any]:
"""测量并发请求性能"""
async def make_request():
return await self.measure_request_time(client, method, url, **kwargs)
start_time = time.time()
results = await asyncio.gather(*[make_request() for _ in range(concurrency)])
end_time = time.time()
total_time = (end_time - start_time) * 1000 # 毫秒
response_times = results
return {
"concurrency": concurrency,
"total_time_ms": total_time,
"response_times_ms": response_times,
"min_time_ms": min(response_times),
"max_time_ms": max(response_times),
"avg_time_ms": statistics.mean(response_times),
"median_time_ms": statistics.median(response_times),
"p95_time_ms": self._percentile(response_times, 95),
"p99_time_ms": self._percentile(response_times, 99),
"throughput_rps": concurrency / (total_time / 1000),
"success_count": len(response_times),
}
def _percentile(self, data: List[float], percentile: float) -> float:
"""计算百分位数"""
sorted_data = sorted(data)
index = int(len(sorted_data) * percentile / 100)
return sorted_data[min(index, len(sorted_data) - 1)]
def assert_performance(self, results: Dict[str, Any], thresholds: Dict[str, Any]):
"""断言性能指标"""
p95_time = results["p95_time_ms"]
p99_time = results["p99_time_ms"]
throughput = results["throughput_rps"]
if p95_time > thresholds["response_time_p95"]:
pytest.fail(f"P95响应时间 {p95_time:.2f}ms 超过阈值 {thresholds['response_time_p95']}ms")
if p99_time > thresholds["response_time_p99"]:
pytest.fail(f"P99响应时间 {p99_time:.2f}ms 超过阈值 {thresholds['response_time_p99']}ms")
if throughput < thresholds["throughput_min"]:
pytest.fail(f"吞吐量 {throughput:.2f} rps 低于最小值 {thresholds['throughput_min']} rps")
logger.info(f"性能测试通过: P95={p95_time:.2f}ms, P99={p99_time:.2f}ms, 吞吐量={throughput:.2f} rps")
@pytest.mark.performance
@pytest.mark.slow
class TestAPIPerformance(PerformanceTest):
"""API性能测试"""
@pytest.mark.asyncio
async def test_user_list_performance(self, perf_client: AsyncClient, performance_thresholds):
"""测试用户列表API性能"""
results = await self.measure_concurrent_requests(
perf_client, "GET", "/api/users", concurrency=20
)
self.assert_performance(results, performance_thresholds)
logger.info(f"用户列表API性能: {results}")
@pytest.mark.asyncio
async def test_role_list_performance(self, perf_client: AsyncClient, performance_thresholds):
"""测试角色列表API性能"""
results = await self.measure_concurrent_requests(
perf_client, "GET", "/api/roles", concurrency=20
)
self.assert_performance(results, performance_thresholds)
logger.info(f"角色列表API性能: {results}")
@pytest.mark.asyncio
async def test_notice_list_performance(self, perf_client: AsyncClient, performance_thresholds):
"""测试通知列表API性能"""
results = await self.measure_concurrent_requests(
perf_client, "GET", "/api/notices", concurrency=20
)
self.assert_performance(results, performance_thresholds)
logger.info(f"通知列表API性能: {results}")
@pytest.mark.asyncio
async def test_search_performance(self, perf_client: AsyncClient, performance_thresholds):
"""测试搜索API性能"""
results = await self.measure_concurrent_requests(
perf_client, "GET", "/api/users/page?keyword=test", concurrency=15
)
self.assert_performance(results, performance_thresholds)
logger.info(f"搜索API性能: {results}")
@pytest.mark.performance
@pytest.mark.slow
class TestLoadTesting(PerformanceTest):
"""负载测试"""
@pytest.mark.asyncio
async def test_sustained_load(self, perf_client: AsyncClient):
"""测试持续负载"""
duration_seconds = 30
requests_per_second = 5
total_requests = duration_seconds * requests_per_second
response_times = []
start_time = time.time()
for i in range(total_requests):
response_time = await self.measure_request_time(
perf_client, "GET", "/api/users"
)
response_times.append(response_time)
elapsed = time.time() - start_time
if elapsed < duration_seconds:
sleep_time = max(0, (i + 1) / requests_per_second - elapsed)
await asyncio.sleep(max(0, sleep_time))
avg_time = statistics.mean(response_times)
p95_time = self._percentile(response_times, 95)
logger.info(f"持续负载测试 - 平均响应时间: {avg_time:.2f}ms, P95: {p95_time:.2f}ms")
assert avg_time < 3000, f"平均响应时间 {avg_time:.2f}ms 超过阈值 3000ms"
assert p95_time < 5000, f"P95响应时间 {p95_time:.2f}ms 超过阈值 5000ms"
@pytest.mark.asyncio
async def test_spike_load(self, perf_client: AsyncClient):
"""测试突发负载"""
spike_sizes = [10, 50, 100, 50, 10]
for spike_size in spike_sizes:
results = await self.measure_concurrent_requests(
perf_client, "GET", "/api/users", concurrency=spike_size
)
logger.info(f"突发负载测试 (并发={spike_size}): P95={results['p95_time_ms']:.2f}ms")
assert results["p95_time_ms"] < 10000, \
f"突发负载 {spike_size} 并发时 P95响应时间超时"