Files
whale-town-end/test/location_broadcast/position_update.perf_spec.ts
moyin 71bc317c57 test:添加位置广播系统端到端测试
- 添加并发用户测试场景
- 实现数据库恢复集成测试
- 重命名登录测试文件以符合命名规范
2026-01-08 23:06:11 +08:00

515 lines
16 KiB
TypeScript

/**
* 位置更新性能测试
*
* 功能描述:
* - 测试位置更新的性能指标
* - 验证系统在高负载下的表现
* - 确保响应时间满足要求
* - 提供性能基准数据
*
* 测试指标:
* - 位置更新响应时间
* - 并发用户处理能力
* - 内存使用情况
* - 系统吞吐量
*
* 最近修改:
* - 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);
});
});
});