/** * 位置更新性能测试 * * 功能描述: * - 测试位置更新的性能指标 * - 验证系统在高负载下的表现 * - 确保响应时间满足要求 * - 提供性能基准数据 * * 测试指标: * - 位置更新响应时间 * - 并发用户处理能力 * - 内存使用情况 * - 系统吞吐量 * * 最近修改: * - 2026-01-08: 文件重命名 - 修正kebab-case为snake_case命名规范 (修改者: moyin) * * @author moyin * @version 1.0.1 * @since 2026-01-08 * @lastModified 2026-01-08 */ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { io, Socket } from 'socket.io-client'; import { LocationBroadcastModule } from '../../location_broadcast.module'; import { RedisModule } from '../../../../core/redis/redis.module'; import { LoginCoreModule } from '../../../../core/login_core/login_core.module'; import { UserProfilesModule } from '../../../../core/db/user_profiles/user_profiles.module'; describe('位置更新性能测试', () => { let app: INestApplication; let authToken: string; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ LocationBroadcastModule, RedisModule, LoginCoreModule, UserProfilesModule.forMemory(), ], }).compile(); app = moduleFixture.createNestApplication(); await app.init(); await app.listen(0); authToken = 'test-jwt-token'; }); afterAll(async () => { await app.close(); }); describe('单用户位置更新性能', () => { let client: Socket; beforeEach((done) => { const port = app.getHttpServer().address().port; client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client.on('connect', () => { client.emit('join_session', { type: 'join_session', sessionId: 'perf-session-001', initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); }); client.on('session_joined', () => { done(); }); }); afterEach(() => { if (client) { client.disconnect(); } }); it('应该在100ms内响应位置更新', (done) => { const startTime = Date.now(); client.emit('position_update', { type: 'position_update', x: 150, y: 250, mapId: 'plaza', timestamp: startTime, }); client.on('position_update_success', () => { const responseTime = Date.now() - startTime; console.log(`位置更新响应时间: ${responseTime}ms`); expect(responseTime).toBeLessThan(100); done(); }); client.on('error', (error) => { done(error); }); }); it('应该支持高频率位置更新', (done) => { const updateCount = 100; let completedUpdates = 0; const startTime = Date.now(); for (let i = 0; i < updateCount; i++) { const updateStartTime = Date.now(); client.emit('position_update', { type: 'position_update', x: 100 + i, y: 200 + i, mapId: 'plaza', timestamp: updateStartTime, }); } client.on('position_update_success', () => { completedUpdates++; if (completedUpdates === updateCount) { const totalTime = Date.now() - startTime; const avgTime = totalTime / updateCount; console.log(`${updateCount}次位置更新总耗时: ${totalTime}ms`); console.log(`平均每次更新耗时: ${avgTime}ms`); console.log(`更新频率: ${(updateCount / totalTime * 1000).toFixed(2)} updates/sec`); expect(avgTime).toBeLessThan(50); // 平均响应时间应小于50ms done(); } }); client.on('error', (error) => { done(error); }); // 超时保护 const timeoutId = setTimeout(() => { if (completedUpdates < updateCount) { done(new Error(`只完成了 ${completedUpdates}/${updateCount} 次更新`)); } }, 10000); }); }); describe('多用户并发性能', () => { it('应该支持100个并发用户', (done) => { const userCount = 100; const clients: Socket[] = []; const sessionId = 'perf-session-concurrent'; let connectedUsers = 0; let joinedUsers = 0; let updateResponses = 0; const port = app.getHttpServer().address().port; const startTime = Date.now(); // 创建多个客户端连接 for (let i = 0; i < userCount; i++) { const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); clients.push(client); client.on('connect', () => { connectedUsers++; if (connectedUsers === userCount) { const connectTime = Date.now() - startTime; console.log(`${userCount}个用户连接耗时: ${connectTime}ms`); // 所有用户加入会话 clients.forEach((c, index) => { c.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100 + index, y: 200 + index, mapId: 'plaza', }, }); }); } }); client.on('session_joined', () => { joinedUsers++; if (joinedUsers === userCount) { const joinTime = Date.now() - startTime; console.log(`${userCount}个用户加入会话耗时: ${joinTime}ms`); // 所有用户同时更新位置 clients.forEach((c, index) => { c.emit('position_update', { type: 'position_update', x: 200 + index, y: 300 + index, mapId: 'plaza', }); }); } }); client.on('position_update_success', () => { updateResponses++; if (updateResponses === userCount) { const totalTime = Date.now() - startTime; console.log(`${userCount}个用户完整流程耗时: ${totalTime}ms`); console.log(`平均每用户耗时: ${(totalTime / userCount).toFixed(2)}ms`); // 清理连接 clients.forEach(c => c.disconnect()); expect(totalTime).toBeLessThan(10000); // 总时间应小于10秒 done(); } }); client.on('error', (error) => { clients.forEach(c => c.disconnect()); done(error); }); } // 超时保护 const timeoutId = setTimeout(() => { clients.forEach(c => c.disconnect()); done(new Error(`测试超时,连接用户: ${connectedUsers}, 加入用户: ${joinedUsers}, 更新响应: ${updateResponses}`)); }, 30000); }); it('应该支持持续的位置广播', (done) => { const userCount = 10; const updatesPerUser = 50; const clients: Socket[] = []; const sessionId = 'perf-session-broadcast'; let totalBroadcasts = 0; let expectedBroadcasts = userCount * updatesPerUser * (userCount - 1); // 每次更新广播给其他用户 const port = app.getHttpServer().address().port; const startTime = Date.now(); // 创建多个客户端 for (let i = 0; i < userCount; i++) { const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); clients.push(client); client.on('connect', () => { client.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100 + i * 10, y: 200 + i * 10, mapId: 'plaza', }, }); }); client.on('position_broadcast', () => { totalBroadcasts++; if (totalBroadcasts >= expectedBroadcasts * 0.8) { // 允许80%的广播成功 const totalTime = Date.now() - startTime; const broadcastRate = totalBroadcasts / totalTime * 1000; console.log(`广播测试完成: ${totalBroadcasts}/${expectedBroadcasts} 条广播`); console.log(`总耗时: ${totalTime}ms`); console.log(`广播频率: ${broadcastRate.toFixed(2)} broadcasts/sec`); clients.forEach(c => c.disconnect()); done(); } }); } // 等待所有用户连接后开始更新 const startUpdateTimer = setTimeout(() => { clients.forEach((client, userIndex) => { for (let updateIndex = 0; updateIndex < updatesPerUser; updateIndex++) { const updateTimer = setTimeout(() => { client.emit('position_update', { type: 'position_update', x: 100 + userIndex * 10 + updateIndex, y: 200 + userIndex * 10 + updateIndex, mapId: 'plaza', }); }, updateIndex * 10); // 每10ms发送一次更新 } }); }, 1000); // 超时保护 const timeoutId = setTimeout(() => { clients.forEach(c => c.disconnect()); console.log(`测试超时,收到广播: ${totalBroadcasts}/${expectedBroadcasts}`); done(); }, 20000); }); }); describe('内存和资源使用', () => { it('应该在合理范围内使用内存', async () => { const initialMemory = process.memoryUsage(); const userCount = 50; const clients: Socket[] = []; const port = app.getHttpServer().address().port; // 创建多个连接 for (let i = 0; i < userCount; i++) { const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); clients.push(client); } // 等待连接建立 await new Promise(resolve => setTimeout(resolve, 2000)); const peakMemory = process.memoryUsage(); const memoryIncrease = peakMemory.heapUsed - initialMemory.heapUsed; const memoryPerUser = memoryIncrease / userCount; console.log(`初始内存使用: ${(initialMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`); console.log(`峰值内存使用: ${(peakMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`); console.log(`内存增长: ${(memoryIncrease / 1024 / 1024).toFixed(2)} MB`); console.log(`每用户内存: ${(memoryPerUser / 1024).toFixed(2)} KB`); // 清理连接 clients.forEach(c => c.disconnect()); // 等待清理完成 await new Promise(resolve => setTimeout(resolve, 1000)); const finalMemory = process.memoryUsage(); console.log(`清理后内存: ${(finalMemory.heapUsed / 1024 / 1024).toFixed(2)} MB`); // 每个用户的内存使用应该小于100KB expect(memoryPerUser).toBeLessThan(100 * 1024); }); it('应该正确清理断开连接的用户', (done) => { const port = app.getHttpServer().address().port; const sessionId = 'cleanup-test-session'; const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); client.on('connect', () => { client.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); }); client.on('session_joined', () => { // 突然断开连接 client.disconnect(); // 等待系统清理 setTimeout(() => { // 创建新连接验证清理是否成功 const newClient = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); newClient.on('connect', () => { newClient.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); }); newClient.on('session_joined', (response) => { // 如果能成功加入,说明之前的用户已被清理 expect(response.success).toBe(true); newClient.disconnect(); done(); }); newClient.on('error', (error) => { newClient.disconnect(); done(error); }); }, 2000); }); client.on('error', (error) => { done(error); }); }); }); describe('压力测试', () => { it('应该在高频率更新下保持稳定', (done) => { const port = app.getHttpServer().address().port; const sessionId = 'stress-test-session'; const updateInterval = 10; // 10ms间隔 const testDuration = 5000; // 5秒测试 let updateCount = 0; let responseCount = 0; let errorCount = 0; let updateTimer: NodeJS.Timeout | null = null; let timeoutTimer: NodeJS.Timeout | null = null; const client = io(`http://localhost:${port}/location-broadcast`, { auth: { token: authToken }, transports: ['websocket'], }); const cleanup = () => { if (updateTimer) { clearInterval(updateTimer); updateTimer = null; } if (timeoutTimer) { clearTimeout(timeoutTimer); timeoutTimer = null; } if (client && client.connected) { client.disconnect(); } }; client.on('connect', () => { client.emit('join_session', { type: 'join_session', sessionId, initialPosition: { x: 100, y: 200, mapId: 'plaza' }, }); }); client.on('session_joined', () => { const startTime = Date.now(); updateTimer = setInterval(() => { if (Date.now() - startTime >= testDuration) { clearInterval(updateTimer!); updateTimer = null; // 等待最后的响应 setTimeout(() => { const successRate = (responseCount / updateCount) * 100; const errorRate = (errorCount / updateCount) * 100; console.log(`压力测试结果:`); console.log(`- 发送更新: ${updateCount}`); console.log(`- 成功响应: ${responseCount}`); console.log(`- 错误数量: ${errorCount}`); console.log(`- 成功率: ${successRate.toFixed(2)}%`); console.log(`- 错误率: ${errorRate.toFixed(2)}%`); cleanup(); // 成功率应该大于95% expect(successRate).toBeGreaterThan(95); done(); }, 1000); return; } updateCount++; client.emit('position_update', { type: 'position_update', x: 100 + (updateCount % 100), y: 200 + (updateCount % 100), mapId: 'plaza', }); }, updateInterval); }); client.on('position_update_success', () => { responseCount++; }); client.on('error', () => { errorCount++; }); // 超时保护 timeoutTimer = setTimeout(() => { cleanup(); done(new Error('压力测试超时')); }, testDuration + 5000); }); }); });