omgkit 2.21.7 → 2.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,335 @@
1
+ ---
2
+ name: testing/mutation-testing
3
+ description: Mutation testing with Stryker to verify test quality by introducing code mutations and measuring detection rates
4
+ category: testing
5
+ tags:
6
+ - testing
7
+ - mutation
8
+ - stryker
9
+ - quality
10
+ - coverage
11
+ related_skills:
12
+ - testing/comprehensive-testing
13
+ - testing/vitest
14
+ - methodology/quality-gates
15
+ ---
16
+
17
+ # Mutation Testing
18
+
19
+ Measure test quality by introducing bugs (mutations) and verifying your tests catch them.
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ # Install Stryker
25
+ npm install -D @stryker-mutator/core @stryker-mutator/vitest-runner
26
+
27
+ # Initialize configuration
28
+ npx stryker init
29
+
30
+ # Run mutation testing
31
+ npm run test:mutation
32
+ ```
33
+
34
+ ## Core Concept
35
+
36
+ Mutation testing introduces small changes (mutations) to your code and runs your tests. If tests still pass, they're too weak.
37
+
38
+ ```javascript
39
+ // Original code
40
+ function isAdult(age) {
41
+ return age >= 18;
42
+ }
43
+
44
+ // Mutations Stryker creates:
45
+ function isAdult(age) { return age > 18; } // >= to >
46
+ function isAdult(age) { return age <= 18; } // >= to <=
47
+ function isAdult(age) { return false; } // return false
48
+ function isAdult(age) { return true; } // return true
49
+ ```
50
+
51
+ A good test catches all mutations:
52
+
53
+ ```javascript
54
+ describe('isAdult', () => {
55
+ it('returns true for age 18', () => {
56
+ expect(isAdult(18)).toBe(true); // Catches >= to >
57
+ });
58
+
59
+ it('returns false for age 17', () => {
60
+ expect(isAdult(17)).toBe(false); // Catches >= to <=
61
+ });
62
+
63
+ it('returns true for age 100', () => {
64
+ expect(isAdult(100)).toBe(true); // Catches return false
65
+ });
66
+ });
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ ### stryker.config.json
72
+
73
+ ```json
74
+ {
75
+ "$schema": "https://raw.githubusercontent.com/stryker-mutator/stryker/master/packages/core/schema/stryker-schema.json",
76
+ "packageManager": "npm",
77
+ "testRunner": "vitest",
78
+ "mutate": [
79
+ "src/**/*.js",
80
+ "src/**/*.ts",
81
+ "!src/**/*.test.js",
82
+ "!src/**/*.spec.ts"
83
+ ],
84
+ "reporters": [
85
+ "progress",
86
+ "clear-text",
87
+ "html",
88
+ "json"
89
+ ],
90
+ "htmlReporter": {
91
+ "fileName": "reports/mutation/index.html"
92
+ },
93
+ "thresholds": {
94
+ "high": 80,
95
+ "low": 60,
96
+ "break": 50
97
+ },
98
+ "concurrency": 4,
99
+ "timeoutMS": 60000
100
+ }
101
+ ```
102
+
103
+ ## Mutation Operators
104
+
105
+ ### Arithmetic Operators
106
+ ```javascript
107
+ // Original: a + b
108
+ a - b // Plus to Minus
109
+ a * b // Plus to Times
110
+ a / b // Plus to Divide
111
+ ```
112
+
113
+ ### Comparison Operators
114
+ ```javascript
115
+ // Original: a > b
116
+ a >= b // Greater to GreaterOrEqual
117
+ a < b // Greater to Less
118
+ a <= b // Greater to LessOrEqual
119
+ a == b // Greater to Equal
120
+ ```
121
+
122
+ ### Logical Operators
123
+ ```javascript
124
+ // Original: a && b
125
+ a || b // And to Or
126
+
127
+ // Original: !a
128
+ a // Negate removal
129
+ ```
130
+
131
+ ### Boundary Mutations
132
+ ```javascript
133
+ // Original: i < 10
134
+ i <= 10 // Less to LessOrEqual
135
+ i < 11 // Boundary change
136
+ i < 9 // Boundary change
137
+ ```
138
+
139
+ ### Return Value Mutations
140
+ ```javascript
141
+ // Original: return value
142
+ return undefined; // Remove return
143
+ return !value; // Negate return
144
+ return ""; // Empty string
145
+ return 0; // Zero
146
+ return null; // Null
147
+ ```
148
+
149
+ ## Understanding Results
150
+
151
+ ### Mutation States
152
+
153
+ | State | Description | Action |
154
+ |-------|-------------|--------|
155
+ | **Killed** | Test failed = mutation caught | Good! |
156
+ | **Survived** | Tests passed = mutation missed | Add tests |
157
+ | **Timeout** | Tests took too long | Check infinite loops |
158
+ | **No Coverage** | No tests cover this code | Add tests |
159
+ | **Compile Error** | Mutation broke compilation | Ignore |
160
+
161
+ ### Mutation Score
162
+
163
+ ```
164
+ Mutation Score = (Killed / Total) * 100%
165
+ ```
166
+
167
+ - **80%+**: Excellent test quality
168
+ - **60-80%**: Good, room for improvement
169
+ - **40-60%**: Weak tests, many gaps
170
+ - **<40%**: Critical test deficiency
171
+
172
+ ## Improving Mutation Score
173
+
174
+ ### 1. Boundary Testing
175
+
176
+ ```javascript
177
+ // Weak: Only tests middle values
178
+ it('validates age', () => {
179
+ expect(isValidAge(25)).toBe(true);
180
+ });
181
+
182
+ // Strong: Tests boundaries
183
+ it('validates age boundaries', () => {
184
+ expect(isValidAge(0)).toBe(true); // Min boundary
185
+ expect(isValidAge(-1)).toBe(false); // Below min
186
+ expect(isValidAge(150)).toBe(true); // Max boundary
187
+ expect(isValidAge(151)).toBe(false); // Above max
188
+ });
189
+ ```
190
+
191
+ ### 2. Condition Coverage
192
+
193
+ ```javascript
194
+ // Original
195
+ function process(a, b) {
196
+ if (a > 0 && b > 0) {
197
+ return 'both positive';
198
+ }
199
+ return 'not both positive';
200
+ }
201
+
202
+ // Weak: Only tests one path
203
+ it('processes positive', () => {
204
+ expect(process(1, 1)).toBe('both positive');
205
+ });
206
+
207
+ // Strong: Tests all conditions
208
+ it('processes various combinations', () => {
209
+ expect(process(1, 1)).toBe('both positive');
210
+ expect(process(-1, 1)).toBe('not both positive'); // a negative
211
+ expect(process(1, -1)).toBe('not both positive'); // b negative
212
+ expect(process(0, 1)).toBe('not both positive'); // a zero
213
+ });
214
+ ```
215
+
216
+ ### 3. Return Value Testing
217
+
218
+ ```javascript
219
+ // Weak: Only tests truthy
220
+ it('checks admin', () => {
221
+ expect(isAdmin(adminUser)).toBeTruthy();
222
+ });
223
+
224
+ // Strong: Tests exact values
225
+ it('checks admin status', () => {
226
+ expect(isAdmin(adminUser)).toBe(true);
227
+ expect(isAdmin(regularUser)).toBe(false);
228
+ expect(isAdmin(null)).toBe(false);
229
+ });
230
+ ```
231
+
232
+ ## Incremental Mutation Testing
233
+
234
+ For large codebases, run mutations on changed files only:
235
+
236
+ ```bash
237
+ # Only mutate changed files
238
+ git diff --name-only origin/main | xargs npx stryker run --mutate
239
+ ```
240
+
241
+ ## CI Integration
242
+
243
+ ### GitHub Actions
244
+
245
+ ```yaml
246
+ name: Mutation Testing
247
+
248
+ on:
249
+ push:
250
+ branches: [main]
251
+ pull_request:
252
+
253
+ jobs:
254
+ mutation:
255
+ runs-on: ubuntu-latest
256
+ steps:
257
+ - uses: actions/checkout@v4
258
+
259
+ - name: Setup Node.js
260
+ uses: actions/setup-node@v4
261
+ with:
262
+ node-version: '20'
263
+ cache: 'npm'
264
+
265
+ - run: npm ci
266
+
267
+ - name: Run Mutation Tests
268
+ run: npm run test:mutation
269
+
270
+ - name: Upload Report
271
+ uses: actions/upload-artifact@v4
272
+ with:
273
+ name: mutation-report
274
+ path: reports/mutation/
275
+ ```
276
+
277
+ ## Performance Optimization
278
+
279
+ ### Reduce Mutation Scope
280
+
281
+ ```json
282
+ {
283
+ "mutate": [
284
+ "src/core/**/*.js",
285
+ "!src/core/**/*.test.js",
286
+ "!src/core/generated/**"
287
+ ]
288
+ }
289
+ ```
290
+
291
+ ### Increase Parallelism
292
+
293
+ ```json
294
+ {
295
+ "concurrency": 8,
296
+ "testRunner": "vitest"
297
+ }
298
+ ```
299
+
300
+ ### Filter Mutators
301
+
302
+ ```json
303
+ {
304
+ "mutator": {
305
+ "excludedMutations": [
306
+ "StringLiteral",
307
+ "ObjectLiteral"
308
+ ]
309
+ }
310
+ }
311
+ ```
312
+
313
+ ## When to Use
314
+
315
+ ### Good Candidates
316
+ - Critical business logic
317
+ - Security-sensitive code
318
+ - Mathematical calculations
319
+ - State machines
320
+ - Validation logic
321
+
322
+ ### When to Skip
323
+ - Generated code
324
+ - Configuration files
325
+ - Third-party wrappers
326
+ - UI components
327
+ - Test utilities
328
+
329
+ ## Anti-Patterns
330
+
331
+ 1. **Chasing 100%**: Diminishing returns above 90%
332
+ 2. **Ignoring Timeouts**: Fix infinite loop mutations
333
+ 3. **Testing Everything**: Focus on critical paths
334
+ 4. **No Baseline**: Establish baseline before improving
335
+ 5. **Infrequent Runs**: Run on every PR
@@ -0,0 +1,361 @@
1
+ ---
2
+ name: testing/performance-testing
3
+ description: Performance testing patterns including load testing, stress testing, benchmarking, and profiling for optimal application performance
4
+ category: testing
5
+ tags:
6
+ - testing
7
+ - performance
8
+ - benchmarking
9
+ - load-testing
10
+ - profiling
11
+ related_skills:
12
+ - testing/comprehensive-testing
13
+ - devops/observability
14
+ - backend/caching-strategies
15
+ ---
16
+
17
+ # Performance Testing
18
+
19
+ Comprehensive performance testing for speed, scalability, and resource efficiency.
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ # Run benchmarks
25
+ npm run test:performance
26
+
27
+ # Profile with Node.js
28
+ node --prof app.js
29
+ node --prof-process isolate-*.log > profile.txt
30
+
31
+ # Load testing with k6
32
+ k6 run load-test.js
33
+ ```
34
+
35
+ ## Types of Performance Tests
36
+
37
+ ### 1. Benchmark Tests
38
+
39
+ Measure execution time of specific operations.
40
+
41
+ ```javascript
42
+ import { describe, it, expect } from 'vitest';
43
+
44
+ describe('Performance Benchmarks', () => {
45
+ function timeExecution(fn, iterations = 1000) {
46
+ const start = performance.now();
47
+ for (let i = 0; i < iterations; i++) {
48
+ fn();
49
+ }
50
+ const end = performance.now();
51
+ return (end - start) / iterations;
52
+ }
53
+
54
+ it('sorts 1000 items under 1ms', () => {
55
+ const data = Array(1000).fill().map(() => Math.random());
56
+ const time = timeExecution(() => [...data].sort((a, b) => a - b), 100);
57
+ expect(time).toBeLessThan(1);
58
+ });
59
+
60
+ it('hashes string under 0.1ms', () => {
61
+ const time = timeExecution(() => hashString('test-string'), 10000);
62
+ expect(time).toBeLessThan(0.1);
63
+ });
64
+
65
+ it('parses JSON under 0.5ms', () => {
66
+ const json = JSON.stringify({ users: Array(100).fill({ name: 'test' }) });
67
+ const time = timeExecution(() => JSON.parse(json), 1000);
68
+ expect(time).toBeLessThan(0.5);
69
+ });
70
+ });
71
+ ```
72
+
73
+ ### 2. Load Tests
74
+
75
+ Simulate concurrent users to test scalability.
76
+
77
+ ```javascript
78
+ // k6 load test script
79
+ import http from 'k6/http';
80
+ import { check, sleep } from 'k6';
81
+
82
+ export const options = {
83
+ stages: [
84
+ { duration: '1m', target: 50 }, // Ramp up to 50 users
85
+ { duration: '3m', target: 50 }, // Stay at 50 users
86
+ { duration: '1m', target: 100 }, // Ramp up to 100
87
+ { duration: '3m', target: 100 }, // Stay at 100
88
+ { duration: '1m', target: 0 }, // Ramp down
89
+ ],
90
+ thresholds: {
91
+ http_req_duration: ['p(95)<200'], // 95% under 200ms
92
+ http_req_failed: ['rate<0.01'], // Error rate < 1%
93
+ },
94
+ };
95
+
96
+ export default function () {
97
+ const res = http.get('https://api.example.com/users');
98
+
99
+ check(res, {
100
+ 'status is 200': (r) => r.status === 200,
101
+ 'response time < 200ms': (r) => r.timings.duration < 200,
102
+ });
103
+
104
+ sleep(1);
105
+ }
106
+ ```
107
+
108
+ ### 3. Stress Tests
109
+
110
+ Push system beyond normal limits.
111
+
112
+ ```javascript
113
+ describe('Stress Tests', () => {
114
+ it('handles 10000 concurrent connections', async () => {
115
+ const connections = Array(10000).fill().map(async () => {
116
+ const ws = new WebSocket('ws://localhost:3000');
117
+ return new Promise((resolve, reject) => {
118
+ ws.onopen = () => resolve(ws);
119
+ ws.onerror = reject;
120
+ setTimeout(() => reject(new Error('timeout')), 5000);
121
+ });
122
+ });
123
+
124
+ const results = await Promise.allSettled(connections);
125
+ const successful = results.filter(r => r.status === 'fulfilled');
126
+ expect(successful.length).toBeGreaterThan(9000);
127
+ });
128
+
129
+ it('recovers after memory pressure', async () => {
130
+ const allocations = [];
131
+
132
+ // Allocate memory
133
+ for (let i = 0; i < 100; i++) {
134
+ allocations.push(new Array(1000000).fill('x'));
135
+ }
136
+
137
+ // Force GC and clear
138
+ allocations.length = 0;
139
+ if (global.gc) global.gc();
140
+
141
+ // System should still function
142
+ const result = await api.get('/health');
143
+ expect(result.status).toBe(200);
144
+ });
145
+ });
146
+ ```
147
+
148
+ ### 4. Memory Tests
149
+
150
+ Detect memory leaks and inefficiencies.
151
+
152
+ ```javascript
153
+ describe('Memory Tests', () => {
154
+ it('does not leak memory on repeated operations', async () => {
155
+ const before = process.memoryUsage().heapUsed;
156
+
157
+ for (let i = 0; i < 10000; i++) {
158
+ await processRequest({ data: 'test' });
159
+ }
160
+
161
+ if (global.gc) global.gc();
162
+ await new Promise(r => setTimeout(r, 100));
163
+
164
+ const after = process.memoryUsage().heapUsed;
165
+ const leaked = after - before;
166
+
167
+ expect(leaked).toBeLessThan(10 * 1024 * 1024); // 10MB
168
+ });
169
+
170
+ it('releases event listeners', () => {
171
+ const emitter = new EventEmitter();
172
+ const before = emitter.listenerCount('event');
173
+
174
+ for (let i = 0; i < 100; i++) {
175
+ const handler = () => {};
176
+ emitter.on('event', handler);
177
+ emitter.off('event', handler);
178
+ }
179
+
180
+ const after = emitter.listenerCount('event');
181
+ expect(after).toBe(before);
182
+ });
183
+ });
184
+ ```
185
+
186
+ ## API Response Time Tests
187
+
188
+ ```javascript
189
+ describe('API Performance', () => {
190
+ const SLA = {
191
+ p50: 50, // 50th percentile under 50ms
192
+ p95: 200, // 95th percentile under 200ms
193
+ p99: 500, // 99th percentile under 500ms
194
+ };
195
+
196
+ it('meets response time SLA', async () => {
197
+ const times = [];
198
+
199
+ for (let i = 0; i < 1000; i++) {
200
+ const start = performance.now();
201
+ await api.get('/users');
202
+ times.push(performance.now() - start);
203
+ }
204
+
205
+ times.sort((a, b) => a - b);
206
+
207
+ const p50 = times[Math.floor(times.length * 0.50)];
208
+ const p95 = times[Math.floor(times.length * 0.95)];
209
+ const p99 = times[Math.floor(times.length * 0.99)];
210
+
211
+ expect(p50).toBeLessThan(SLA.p50);
212
+ expect(p95).toBeLessThan(SLA.p95);
213
+ expect(p99).toBeLessThan(SLA.p99);
214
+ });
215
+ });
216
+ ```
217
+
218
+ ## Database Performance Tests
219
+
220
+ ```javascript
221
+ describe('Database Performance', () => {
222
+ it('query executes under 10ms', async () => {
223
+ const start = performance.now();
224
+ await db.query('SELECT * FROM users WHERE id = ?', [1]);
225
+ const duration = performance.now() - start;
226
+ expect(duration).toBeLessThan(10);
227
+ });
228
+
229
+ it('handles 100 concurrent queries', async () => {
230
+ const queries = Array(100).fill().map(() =>
231
+ db.query('SELECT * FROM users LIMIT 10')
232
+ );
233
+
234
+ const start = performance.now();
235
+ await Promise.all(queries);
236
+ const duration = performance.now() - start;
237
+
238
+ expect(duration).toBeLessThan(1000);
239
+ });
240
+
241
+ it('insert batch is efficient', async () => {
242
+ const records = Array(1000).fill().map((_, i) => ({
243
+ name: `User ${i}`,
244
+ email: `user${i}@test.com`,
245
+ }));
246
+
247
+ const start = performance.now();
248
+ await db.batchInsert('users', records);
249
+ const duration = performance.now() - start;
250
+
251
+ expect(duration).toBeLessThan(500);
252
+ });
253
+ });
254
+ ```
255
+
256
+ ## Profiling Patterns
257
+
258
+ ### CPU Profiling
259
+
260
+ ```javascript
261
+ const v8Profiler = require('v8-profiler-next');
262
+
263
+ async function profileCPU(fn, name) {
264
+ v8Profiler.startProfiling(name);
265
+
266
+ await fn();
267
+
268
+ const profile = v8Profiler.stopProfiling(name);
269
+ profile.export((error, result) => {
270
+ fs.writeFileSync(`${name}.cpuprofile`, result);
271
+ });
272
+ }
273
+ ```
274
+
275
+ ### Memory Profiling
276
+
277
+ ```javascript
278
+ function takeHeapSnapshot(name) {
279
+ const snapshot = v8.writeHeapSnapshot();
280
+ console.log(`Heap snapshot written to ${snapshot}`);
281
+ }
282
+
283
+ // In test
284
+ it('memory usage is stable', async () => {
285
+ takeHeapSnapshot('before');
286
+
287
+ for (let i = 0; i < 10000; i++) {
288
+ await processData();
289
+ }
290
+
291
+ if (global.gc) global.gc();
292
+ takeHeapSnapshot('after');
293
+ });
294
+ ```
295
+
296
+ ## Performance Budgets
297
+
298
+ ```javascript
299
+ const budgets = {
300
+ 'First Contentful Paint': 1500,
301
+ 'Time to Interactive': 3500,
302
+ 'Total Bundle Size': 250 * 1024,
303
+ 'API Response Time': 200,
304
+ };
305
+
306
+ describe('Performance Budgets', () => {
307
+ it('bundle size within budget', async () => {
308
+ const stats = require('./dist/stats.json');
309
+ const totalSize = stats.assets
310
+ .filter(a => a.name.endsWith('.js'))
311
+ .reduce((sum, a) => sum + a.size, 0);
312
+
313
+ expect(totalSize).toBeLessThan(budgets['Total Bundle Size']);
314
+ });
315
+ });
316
+ ```
317
+
318
+ ## Continuous Performance Testing
319
+
320
+ ### GitHub Actions
321
+
322
+ ```yaml
323
+ name: Performance Tests
324
+
325
+ on:
326
+ push:
327
+ branches: [main]
328
+ pull_request:
329
+
330
+ jobs:
331
+ performance:
332
+ runs-on: ubuntu-latest
333
+ steps:
334
+ - uses: actions/checkout@v4
335
+ - uses: actions/setup-node@v4
336
+ with:
337
+ node-version: '20'
338
+
339
+ - run: npm ci
340
+ - run: npm run test:performance
341
+
342
+ - name: Compare with baseline
343
+ run: |
344
+ npm run benchmark:compare -- --baseline main
345
+ ```
346
+
347
+ ## When to Use
348
+
349
+ - Before major releases
350
+ - After performance-critical changes
351
+ - During capacity planning
352
+ - When SLA monitoring shows degradation
353
+ - As part of CI/CD pipeline
354
+
355
+ ## Anti-Patterns
356
+
357
+ 1. **Testing on Development Hardware**: Use production-like environment
358
+ 2. **Ignoring Warmup**: JIT compilation affects first runs
359
+ 3. **Single Metric Focus**: Monitor multiple dimensions
360
+ 4. **No Baseline**: Always compare against baseline
361
+ 5. **Flaky Thresholds**: Use percentiles, not averages