""" 性能测试基础框架 """ 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响应时间超时"