Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Performance Testing - próby wytrzymałościowe

Witaj, inżynierze wytrzymałości! Konsul Caesar.js musi wiedzieć, czy nasz fort wytrzyma najcięższe oblężenia, najdłuższe kampanie i najbardziej intensywne bitwy. Performance testing sprawdza granice wytrzymałości naszego systemu!

Czym są testy wydajnościowe?

Performance testing to jak stress test fortu przed wyruszeniem na niebezpieczną kampanię:

  • Load Testing - normalne warunki marszu
  • Stress Testing - ekstremalne warunki (oblężenie, bitwa)
  • Spike Testing - nagłe obciążenia (atak wielu barbarzyńców)
  • Volume Testing - duże ilości danych (pełny ładunek tributów)
  • Endurance Testing - długotrwałe operacje (miesięczna kampania)
1// Przykład prostego testu wydajności
2describe('Tribute Processing Performance', () => {
3 it('should process 1000 tributes within 500ms', async () => {
4 const tributes = Array.from({ length: 1000 }, (_, i) => ({
5 id: i,
6 name: `Tribute ${i}`,
7 value: Math.random() * 1000
8 }));
9 
10 const startTime = performance.now();
11 const result = await tributeService.processBatch(tributes);
12 const endTime = performance.now();
13 
14 const duration = endTime - startTime;
15 
16 expect(duration).toBeLessThan(500);
17 expect(result.processed).toBe(1000);
18 expect(result.errors).toBe(0);
19 });
20});

Load Testing - normalne obciążenie

Test normalnego ruchu

1// load-test.spec.ts
2describe('Normal Load Tests', () => {
3 let app: INestApplication;
4 
5 beforeAll(async () => {
6 app = await createTestApp();
7 });
8 
9 it('should handle 100 concurrent tribute requests', async () => {
10 const concurrentRequests = 100;
11 const requests = [];
12 
13 // Przygotuj 100 równoczesnych żądań
14 for (let i = 0; i < concurrentRequests; i++) {
15 requests.push(
16 request(app.getHttpServer())
17 .get('/tributes')
18 .expect(200)
19 );
20 }
21 
22 const startTime = Date.now();
23 const responses = await Promise.allSettled(requests);
24 const endTime = Date.now();
25 
26 const successfulRequests = responses.filter(r => r.status === 'fulfilled').length;
27 const averageResponseTime = (endTime - startTime) / concurrentRequests;
28 
29 expect(successfulRequests).toBeGreaterThan(95); // 95% success rate
30 expect(averageResponseTime).toBeLessThan(100); // Średni czas < 100ms
31 });
32 
33 it('should maintain database performance under load', async () => {
34 const operationsCount = 500;
35 const operations = [];
36 
37 // Mix różnych operacji bazy danych
38 for (let i = 0; i < operationsCount; i++) {
39 if (i % 5 === 0) {
40 // 20% CREATE operations
41 operations.push(
42 tributeService.create({
43 name: `Load Test Tribute ${i}`,
44 value: Math.random() * 1000
45 })
46 );
47 } else if (i % 5 === 1) {
48 // 20% UPDATE operations
49 operations.push(
50 tributeService.update(i, { value: Math.random() * 1000 })
51 );
52 } else {
53 // 60% READ operations
54 operations.push(tributeService.findAll());
55 }
56 }
57 
58 const startTime = Date.now();
59 const results = await Promise.allSettled(operations);
60 const endTime = Date.now();
61 
62 const totalTime = endTime - startTime;
63 const successfulOperations = results.filter(r => r.status === 'fulfilled').length;
64 const throughput = successfulOperations / (totalTime / 1000); // ops/sec
65 
66 expect(successfulOperations).toBeGreaterThan(475); // 95% success
67 expect(throughput).toBeGreaterThan(50); // min 50 ops/sec
68 expect(totalTime).toBeLessThan(10000); // max 10 sekund
69 });
70});

Stress Testing - granice wytrzymałości

Finding Breaking Points

1describe('Stress Tests', () => {
2 it('should find the breaking point of expedition service', async () => {
3 const results = [];
4 let currentLoad = 10;
5 let maxSuccessfulLoad = 0;
6 
7 // Zwiększaj obciążenie aż system się załamie
8 while (currentLoad <= 1000) {
9 const expeditions = Array.from({ length: currentLoad }, (_, i) => ({
10 destination: `Test Island ${i}`,
11 legionSize: 10,
12 duration: 1
13 }));
14 
15 try {
16 const startTime = Date.now();
17 const promises = expeditions.map(exp => 
18 expeditionService.planExpedition(exp)
19 );
20 
21 const responses = await Promise.allSettled(promises);
22 const endTime = Date.now();
23 
24 const successCount = responses.filter(r => r.status === 'fulfilled').length;
25 const successRate = successCount / currentLoad;
26 const avgResponseTime = (endTime - startTime) / currentLoad;
27 
28 results.push({
29 load: currentLoad,
30 successRate,
31 avgResponseTime,
32 totalTime: endTime - startTime
33 });
34 
35 // Jeśli success rate spadł poniżej 90% lub czas odpowiedzi > 5s
36 if (successRate < 0.9 || avgResponseTime > 5000) {
37 break;
38 }
39 
40 maxSuccessfulLoad = currentLoad;
41 currentLoad = Math.floor(currentLoad * 1.5); // Zwiększ o 50%
42 
43 } catch (error) {
44 console.log(`System failed at load: ${currentLoad}`);
45 break;
46 }
47 }
48 
49 console.log('Performance Results:', results);
50 console.log(`Maximum successful load: ${maxSuccessfulLoad}`);
51 
52 expect(maxSuccessfulLoad).toBeGreaterThan(50); // System should handle at least 50
53 });
54 
55 it('should handle memory pressure gracefully', async () => {
56 const initialMemory = process.memoryUsage().heapUsed;
57 const largeDataSets = [];
58 
59 // Tworzenie dużych setów danych
60 for (let i = 0; i < 100; i++) {
61 const largeExpedition = {
62 id: i,
63 participants: Array.from({ length: 1000 }, (_, j) => ({
64 id: j,
65 name: `Legionary ${j}`,
66 skills: Array.from({ length: 50 }, () => Math.random()),
67 equipment: Array.from({ length: 20 }, () => `Item ${Math.random()}`)
68 })),
69 tributes: Array.from({ length: 500 }, (_, k) => ({
70 id: k,
71 name: `Tribute ${k}`,
72 description: 'A'.repeat(1000), // 1KB string
73 metadata: { /* complex object */ }
74 }))
75 };
76 
77 largeDataSets.push(largeExpedition);
78 
79 // Process data
80 await expeditionService.processExpeditionData(largeExpedition);
81 
82 // Check memory usage
83 const currentMemory = process.memoryUsage().heapUsed;
84 const memoryIncrease = currentMemory - initialMemory;
85 
86 // If memory increased by more than 100MB, run garbage collection
87 if (memoryIncrease > 100 * 1024 * 1024) {
88 global.gc?.();
89 }
90 }
91 
92 // Force garbage collection
93 global.gc?.();
94 
95 const finalMemory = process.memoryUsage().heapUsed;
96 const totalMemoryIncrease = finalMemory - initialMemory;
97 
98 // Memory increase should be reasonable (< 50MB)
99 expect(totalMemoryIncrease).toBeLessThan(50 * 1024 * 1024);
100 });
101});

Spike Testing - nagłe obciążenia

1describe('Spike Tests', () => {
2 it('should handle sudden traffic spikes', async () => {
3 // Baseline - normalne obciążenie
4 const baselineRequests = 10;
5 const baselineTime = await measureRequestTime(baselineRequests);
6 
7 // Spike - nagły wzrost o 1000%
8 const spikeRequests = 100;
9 const spikeStartTime = Date.now();
10 
11 const spikePromises = Array.from({ length: spikeRequests }, () =>
12 request(app.getHttpServer())
13 .post('/expeditions/emergency')
14 .send({ type: 'rescue_mission', priority: 'high' })
15 );
16 
17 const spikeResults = await Promise.allSettled(spikePromises);
18 const spikeEndTime = Date.now();
19 
20 const successfulSpike = spikeResults.filter(r => r.status === 'fulfilled').length;
21 const spikeSuccessRate = successfulSpike / spikeRequests;
22 const spikeDuration = spikeEndTime - spikeStartTime;
23 
24 // Po spike - powrot do normalnego obciążenia
25 await new Promise(resolve => setTimeout(resolve, 2000)); // 2s recovery
26 const recoveryTime = await measureRequestTime(baselineRequests);
27 
28 // Asserts
29 expect(spikeSuccessRate).toBeGreaterThan(0.8); // 80% requests should succeed
30 expect(spikeDuration).toBeLessThan(10000); // Spike processed in <10s
31 expect(recoveryTime).toBeLessThan(baselineTime * 1.5); // Recovery within 150% of baseline
32 });
33});
34
35async function measureRequestTime(requestCount: number): Promise<number> {
36 const startTime = Date.now();
37 
38 const promises = Array.from({ length: requestCount }, () =>
39 request(app.getHttpServer()).get('/health').expect(200)
40 );
41 
42 await Promise.all(promises);
43 
44 return Date.now() - startTime;
45}

Database Performance Testing

1describe('Database Performance', () => {
2 let dataSource: DataSource;
3 
4 beforeAll(async () => {
5 dataSource = app.get(DataSource);
6 });
7 
8 it('should handle large batch operations efficiently', async () => {
9 const batchSize = 10000;
10 const tributes = Array.from({ length: batchSize }, (_, i) => ({
11 name: `Batch Tribute ${i}`,
12 value: Math.random() * 1000,
13 location: `Location ${i % 100}`,
14 collectedAt: new Date()
15 }));
16 
17 // Test batch insert
18 const insertStartTime = Date.now();
19 await dataSource.getRepository(Tribute).save(tributes);
20 const insertTime = Date.now() - insertStartTime;
21 
22 // Test batch read
23 const readStartTime = Date.now();
24 const allTributes = await dataSource.getRepository(Tribute).find();
25 const readTime = Date.now() - readStartTime;
26 
27 // Test complex query
28 const queryStartTime = Date.now();
29 const expensiveTributes = await dataSource
30 .getRepository(Tribute)
31 .createQueryBuilder('tribute')
32 .where('tribute.value > :minValue', { minValue: 500 })
33 .orderBy('tribute.value', 'DESC')
34 .limit(100)
35 .getMany();
36 const queryTime = Date.now() - queryStartTime;
37 
38 // Cleanup
39 await dataSource.getRepository(Tribute).delete({});
40 
41 expect(insertTime).toBeLessThan(5000); // Insert 10k records in <5s
42 expect(readTime).toBeLessThan(1000); // Read all in <1s
43 expect(queryTime).toBeLessThan(100); // Complex query in <100ms
44 expect(allTributes.length).toBe(batchSize);
45 expect(expensiveTributes.length).toBeGreaterThan(0);
46 });
47 
48 it('should maintain performance with concurrent connections', async () => {
49 const connectionCount = 20;
50 const operationsPerConnection = 50;
51 
52 const connectionPromises = Array.from({ length: connectionCount }, async (_, i) => {
53 const operations = [];
54 
55 for (let j = 0; j < operationsPerConnection; j++) {
56 operations.push(
57 dataSource.getRepository(Tribute).findOne({ where: { id: j + 1 } })
58 );
59 }
60 
61 const startTime = Date.now();
62 await Promise.all(operations);
63 const endTime = Date.now();
64 
65 return {
66 connectionId: i,
67 duration: endTime - startTime,
68 operationsCount: operationsPerConnection
69 };
70 });
71 
72 const results = await Promise.all(connectionPromises);
73 
74 const averageDuration = results.reduce((sum, r) => sum + r.duration, 0) / connectionCount;
75 const maxDuration = Math.max(...results.map(r => r.duration));
76 
77 expect(averageDuration).toBeLessThan(2000); // Average < 2s
78 expect(maxDuration).toBeLessThan(5000); // Max < 5s
79 });
80});

Memory i Resource Testing

1describe('Resource Usage Tests', () => {
2 it('should not create memory leaks', async () => {
3 const initialMemory = process.memoryUsage();
4 
5 // Symuluj intensywną pracę
6 for (let cycle = 0; cycle < 10; cycle++) {
7 const tributes = [];
8 
9 // Create many objects
10 for (let i = 0; i < 1000; i++) {
11 tributes.push(await tributeService.create({
12 name: `Cycle ${cycle} Tribute ${i}`,
13 value: Math.random() * 1000
14 }));
15 }
16 
17 // Process them
18 await tributeService.processBatch(tributes);
19 
20 // Clean up
21 await tributeService.deleteMany(tributes.map(t => t.id));
22 
23 // Force garbage collection every few cycles
24 if (cycle % 3 === 0) {
25 global.gc?.();
26 }
27 }
28 
29 // Final cleanup
30 global.gc?.();
31 
32 const finalMemory = process.memoryUsage();
33 const memoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed;
34 
35 // Memory should not increase significantly
36 expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024); // < 10MB
37 });
38 
39 it('should handle file system operations efficiently', async () => {
40 const fileCount = 100;
41 const fileSize = 1024; // 1KB per file
42 
43 // Create test files
44 const createStartTime = Date.now();
45 const filePromises = Array.from({ length: fileCount }, (_, i) => 
46 fileService.createTributeMap(`map-${i}`, 'x'.repeat(fileSize))
47 );
48 await Promise.all(filePromises);
49 const createTime = Date.now() - createStartTime;
50 
51 // Read all files
52 const readStartTime = Date.now();
53 const readPromises = Array.from({ length: fileCount }, (_, i) => 
54 fileService.readTributeMap(`map-${i}`)
55 );
56 const files = await Promise.all(readPromises);
57 const readTime = Date.now() - readStartTime;
58 
59 // Delete all files
60 const deleteStartTime = Date.now();
61 const deletePromises = Array.from({ length: fileCount }, (_, i) => 
62 fileService.deleteTributeMap(`map-${i}`)
63 );
64 await Promise.all(deletePromises);
65 const deleteTime = Date.now() - deleteStartTime;
66 
67 expect(createTime).toBeLessThan(2000); // Create 100 files in <2s
68 expect(readTime).toBeLessThan(1000); // Read 100 files in <1s
69 expect(deleteTime).toBeLessThan(1000); // Delete 100 files in <1s
70 expect(files.length).toBe(fileCount);
71 });
72});

Performance Monitoring w CI/CD

Performance Regression Tests

1// performance-regression.spec.ts
2describe('Performance Regression Tests', () => {
3 const performanceBaselines = {
4 tributeSearch: 50, // ms
5 expeditionPlanning: 200, // ms
6 legionAssignment: 100, // ms
7 batchProcessing: 1000, // ms for 1000 items
8 };
9 
10 it('should not regress tribute search performance', async () => {
11 const iterations = 10;
12 const times = [];
13 
14 for (let i = 0; i < iterations; i++) {
15 const startTime = performance.now();
16 await tributeService.search({
17 minValue: 100,
18 maxValue: 1000,
19 rarity: ['common', 'rare'],
20 location: 'Hispania'
21 });
22 const endTime = performance.now();
23 
24 times.push(endTime - startTime);
25 }
26 
27 const averageTime = times.reduce((sum, time) => sum + time, 0) / iterations;
28 const baseline = performanceBaselines.tributeSearch;
29 
30 // Allow 10% performance degradation
31 const threshold = baseline * 1.1;
32 
33 expect(averageTime).toBeLessThan(threshold);
34 
35 if (averageTime > baseline) {
36 console.warn(`Performance regression detected: ${averageTime}ms vs baseline ${baseline}ms`);
37 }
38 });
39});

Continuous Performance Monitoring

1# .github/workflows/performance.yml
2name: Performance Tests
3
4on:
5 push:
6 branches: [main]
7 pull_request:
8
9jobs:
10 performance:
11 runs-on: ubuntu-latest
12 
13 steps:
14 - uses: actions/checkout@v3
15 
16 - name: Setup Node.js
17 uses: actions/setup-node@v3
18 with:
19 node-version: '18'
20 
21 - name: Install dependencies
22 run: npm ci
23 
24 - name: Start test database
25 run: docker run -d -p 5432:5432 postgres:13
26 
27 - name: Run performance tests
28 run: npm run test:performance
29 
30 - name: Upload performance results
31 uses: actions/upload-artifact@v3
32 with:
33 name: performance-results
34 path: performance-results.json

Performance testing to jak test wytrzymałościowy fortu - pokazuje czy przetrwa najgorsze oblężenia i najcięższe bitwy. Regularne testowanie wydajności to gwarancja, że Twój system będzie gotowy na każde wyzwanie!

Ir a CodeWorlds