claude-mpm 4.17.0__py3-none-any.whl → 4.17.1__py3-none-any.whl
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.
Potentially problematic release.
This version of claude-mpm might be problematic. Click here for more details.
- claude_mpm/VERSION +1 -1
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/METADATA +1 -1
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/RECORD +27 -7
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/WHEEL +0 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.17.0.dist-info → claude_mpm-4.17.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill_id: async-testing
|
|
3
|
+
skill_version: 0.1.0
|
|
4
|
+
description: Patterns for testing asynchronous code across languages, eliminating redundant async testing guidance per agent.
|
|
5
|
+
updated_at: 2025-10-30T17:00:00Z
|
|
6
|
+
tags: [testing, async, asynchronous, concurrency]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Async Testing
|
|
10
|
+
|
|
11
|
+
Patterns for testing asynchronous code across languages. Eliminates ~200-300 lines of redundant async testing guidance per agent.
|
|
12
|
+
|
|
13
|
+
## Core Async Testing Principles
|
|
14
|
+
|
|
15
|
+
### 1. Async Code is Still Testable
|
|
16
|
+
|
|
17
|
+
Asynchronous operations can and should be tested just like synchronous code. The key is understanding the execution model.
|
|
18
|
+
|
|
19
|
+
### 2. Control Time in Tests
|
|
20
|
+
|
|
21
|
+
Never rely on actual timeouts in tests. Use time mocking for deterministic, fast tests.
|
|
22
|
+
|
|
23
|
+
### 3. Test Race Conditions Explicitly
|
|
24
|
+
|
|
25
|
+
Concurrent code has race conditions. Test them deliberately rather than hoping they don't happen.
|
|
26
|
+
|
|
27
|
+
## Language-Specific Patterns
|
|
28
|
+
|
|
29
|
+
### Python (asyncio)
|
|
30
|
+
|
|
31
|
+
#### Basic Async Test Setup
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import pytest
|
|
35
|
+
import asyncio
|
|
36
|
+
|
|
37
|
+
# Mark test as async
|
|
38
|
+
@pytest.mark.asyncio
|
|
39
|
+
async def test_async_operation():
|
|
40
|
+
result = await async_function()
|
|
41
|
+
assert result == expected_value
|
|
42
|
+
|
|
43
|
+
# Alternative: Use asyncio.run()
|
|
44
|
+
def test_async_operation_sync():
|
|
45
|
+
result = asyncio.run(async_function())
|
|
46
|
+
assert result == expected_value
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Testing Async Fixtures
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
async def async_database():
|
|
54
|
+
"""Async fixture for database setup/teardown."""
|
|
55
|
+
db = await create_async_database()
|
|
56
|
+
yield db
|
|
57
|
+
await db.close()
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
async def test_with_async_fixture(async_database):
|
|
61
|
+
result = await async_database.query("SELECT * FROM users")
|
|
62
|
+
assert len(result) > 0
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Testing Concurrent Operations
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
@pytest.mark.asyncio
|
|
69
|
+
async def test_concurrent_requests():
|
|
70
|
+
# Run multiple async operations concurrently
|
|
71
|
+
results = await asyncio.gather(
|
|
72
|
+
fetch_user(1),
|
|
73
|
+
fetch_user(2),
|
|
74
|
+
fetch_user(3)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
assert len(results) == 3
|
|
78
|
+
assert all(user.is_valid() for user in results)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Testing Timeouts
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_operation_timeout():
|
|
86
|
+
with pytest.raises(asyncio.TimeoutError):
|
|
87
|
+
await asyncio.wait_for(slow_operation(), timeout=1.0)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Mocking Async Functions
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from unittest.mock import AsyncMock, patch
|
|
94
|
+
|
|
95
|
+
@pytest.mark.asyncio
|
|
96
|
+
async def test_with_async_mock():
|
|
97
|
+
mock_api = AsyncMock(return_value={"status": "success"})
|
|
98
|
+
|
|
99
|
+
with patch('module.api_call', mock_api):
|
|
100
|
+
result = await function_that_calls_api()
|
|
101
|
+
|
|
102
|
+
assert result["status"] == "success"
|
|
103
|
+
mock_api.assert_called_once()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### JavaScript/TypeScript (async/await)
|
|
107
|
+
|
|
108
|
+
#### Basic Async Test Setup
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
// Jest
|
|
112
|
+
describe('Async Operations', () => {
|
|
113
|
+
test('should handle async operation', async () => {
|
|
114
|
+
const result = await asyncFunction();
|
|
115
|
+
expect(result).toBe(expectedValue);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Alternative: Return promise
|
|
119
|
+
test('should handle promise', () => {
|
|
120
|
+
return asyncFunction().then(result => {
|
|
121
|
+
expect(result).toBe(expectedValue);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### Testing Promise Resolution/Rejection
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
test('should resolve with correct data', async () => {
|
|
131
|
+
await expect(fetchUser(1)).resolves.toEqual({
|
|
132
|
+
id: 1,
|
|
133
|
+
name: 'John'
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('should reject when user not found', async () => {
|
|
138
|
+
await expect(fetchUser(999)).rejects.toThrow('User not found');
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Testing Concurrent Operations
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
test('should handle multiple concurrent requests', async () => {
|
|
146
|
+
const promises = [
|
|
147
|
+
fetchUser(1),
|
|
148
|
+
fetchUser(2),
|
|
149
|
+
fetchUser(3)
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const results = await Promise.all(promises);
|
|
153
|
+
|
|
154
|
+
expect(results).toHaveLength(3);
|
|
155
|
+
expect(results.every(user => user.id > 0)).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Testing Race Conditions
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
test('should handle race condition correctly', async () => {
|
|
163
|
+
let counter = 0;
|
|
164
|
+
const increment = async () => {
|
|
165
|
+
const current = counter;
|
|
166
|
+
await delay(10); // Simulate async work
|
|
167
|
+
counter = current + 1;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Run concurrently - will show race condition
|
|
171
|
+
await Promise.all([increment(), increment(), increment()]);
|
|
172
|
+
|
|
173
|
+
// Without proper synchronization, counter might be 1 instead of 3
|
|
174
|
+
expect(counter).toBe(3); // This test will fail if race condition exists
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Mocking Async Functions
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
jest.mock('./api');
|
|
182
|
+
|
|
183
|
+
test('should use mocked async function', async () => {
|
|
184
|
+
const mockFetchUser = require('./api').fetchUser;
|
|
185
|
+
mockFetchUser.mockResolvedValue({ id: 1, name: 'John' });
|
|
186
|
+
|
|
187
|
+
const result = await getUserData(1);
|
|
188
|
+
|
|
189
|
+
expect(result.name).toBe('John');
|
|
190
|
+
expect(mockFetchUser).toHaveBeenCalledWith(1);
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### Testing Timeouts
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
test('should timeout long operations', async () => {
|
|
198
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
199
|
+
setTimeout(() => reject(new Error('Timeout')), 1000)
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
await expect(
|
|
203
|
+
Promise.race([slowOperation(), timeoutPromise])
|
|
204
|
+
).rejects.toThrow('Timeout');
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Go (goroutines)
|
|
209
|
+
|
|
210
|
+
#### Basic Async Test Setup
|
|
211
|
+
|
|
212
|
+
```go
|
|
213
|
+
func TestAsyncOperation(t *testing.T) {
|
|
214
|
+
done := make(chan bool)
|
|
215
|
+
var result int
|
|
216
|
+
|
|
217
|
+
go func() {
|
|
218
|
+
result = expensiveOperation()
|
|
219
|
+
done <- true
|
|
220
|
+
}()
|
|
221
|
+
|
|
222
|
+
<-done // Wait for completion
|
|
223
|
+
|
|
224
|
+
if result != expected {
|
|
225
|
+
t.Errorf("Expected %d, got %d", expected, result)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### Testing with Timeouts
|
|
231
|
+
|
|
232
|
+
```go
|
|
233
|
+
func TestWithTimeout(t *testing.T) {
|
|
234
|
+
done := make(chan bool)
|
|
235
|
+
|
|
236
|
+
go func() {
|
|
237
|
+
slowOperation()
|
|
238
|
+
done <- true
|
|
239
|
+
}()
|
|
240
|
+
|
|
241
|
+
select {
|
|
242
|
+
case <-done:
|
|
243
|
+
// Success
|
|
244
|
+
case <-time.After(1 * time.Second):
|
|
245
|
+
t.Fatal("Operation timed out")
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Testing Concurrent Operations
|
|
251
|
+
|
|
252
|
+
```go
|
|
253
|
+
func TestConcurrentOperations(t *testing.T) {
|
|
254
|
+
const numWorkers = 10
|
|
255
|
+
results := make(chan int, numWorkers)
|
|
256
|
+
|
|
257
|
+
for i := 0; i < numWorkers; i++ {
|
|
258
|
+
go func(id int) {
|
|
259
|
+
results <- processTask(id)
|
|
260
|
+
}(i)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Collect results
|
|
264
|
+
var sum int
|
|
265
|
+
for i := 0; i < numWorkers; i++ {
|
|
266
|
+
sum += <-results
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if sum != expectedSum {
|
|
270
|
+
t.Errorf("Expected sum %d, got %d", expectedSum, sum)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### Testing Race Conditions
|
|
276
|
+
|
|
277
|
+
```go
|
|
278
|
+
func TestRaceCondition(t *testing.T) {
|
|
279
|
+
// Enable race detector: go test -race
|
|
280
|
+
counter := 0
|
|
281
|
+
done := make(chan bool)
|
|
282
|
+
|
|
283
|
+
for i := 0; i < 100; i++ {
|
|
284
|
+
go func() {
|
|
285
|
+
counter++ // Race condition!
|
|
286
|
+
done <- true
|
|
287
|
+
}()
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
for i := 0; i < 100; i++ {
|
|
291
|
+
<-done
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// With race condition, counter might be < 100
|
|
295
|
+
if counter != 100 {
|
|
296
|
+
t.Errorf("Expected 100, got %d (race condition detected)", counter)
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Rust (async/await with tokio)
|
|
302
|
+
|
|
303
|
+
#### Basic Async Test Setup
|
|
304
|
+
|
|
305
|
+
```rust
|
|
306
|
+
#[tokio::test]
|
|
307
|
+
async fn test_async_operation() {
|
|
308
|
+
let result = async_function().await;
|
|
309
|
+
assert_eq!(result, expected_value);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Multi-threaded runtime
|
|
313
|
+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
314
|
+
async fn test_concurrent_operation() {
|
|
315
|
+
let result = concurrent_operation().await;
|
|
316
|
+
assert!(result.is_ok());
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
#### Testing with Timeouts
|
|
321
|
+
|
|
322
|
+
```rust
|
|
323
|
+
use tokio::time::{timeout, Duration};
|
|
324
|
+
|
|
325
|
+
#[tokio::test]
|
|
326
|
+
async fn test_with_timeout() {
|
|
327
|
+
let result = timeout(
|
|
328
|
+
Duration::from_secs(1),
|
|
329
|
+
slow_operation()
|
|
330
|
+
).await;
|
|
331
|
+
|
|
332
|
+
assert!(result.is_err(), "Operation should have timed out");
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Testing Concurrent Operations
|
|
337
|
+
|
|
338
|
+
```rust
|
|
339
|
+
#[tokio::test]
|
|
340
|
+
async fn test_concurrent_tasks() {
|
|
341
|
+
let task1 = tokio::spawn(async { fetch_data(1).await });
|
|
342
|
+
let task2 = tokio::spawn(async { fetch_data(2).await });
|
|
343
|
+
let task3 = tokio::spawn(async { fetch_data(3).await });
|
|
344
|
+
|
|
345
|
+
let results = tokio::try_join!(task1, task2, task3).unwrap();
|
|
346
|
+
|
|
347
|
+
assert_eq!(results.0, expected1);
|
|
348
|
+
assert_eq!(results.1, expected2);
|
|
349
|
+
assert_eq!(results.2, expected3);
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Common Async Testing Patterns
|
|
354
|
+
|
|
355
|
+
### Pattern 1: Testing Callback-Based Async
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
// Converting callbacks to promises for testing
|
|
359
|
+
function promisify(callbackFn) {
|
|
360
|
+
return (...args) => {
|
|
361
|
+
return new Promise((resolve, reject) => {
|
|
362
|
+
callbackFn(...args, (err, result) => {
|
|
363
|
+
if (err) reject(err);
|
|
364
|
+
else resolve(result);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
test('should handle callback-based async', async () => {
|
|
371
|
+
const asyncFn = promisify(callbackBasedFunction);
|
|
372
|
+
const result = await asyncFn(arg1, arg2);
|
|
373
|
+
expect(result).toBe(expected);
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Pattern 2: Testing Event Emitters
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
test('should emit events in correct order', async () => {
|
|
381
|
+
const events = [];
|
|
382
|
+
const emitter = new EventEmitter();
|
|
383
|
+
|
|
384
|
+
emitter.on('start', () => events.push('start'));
|
|
385
|
+
emitter.on('process', () => events.push('process'));
|
|
386
|
+
emitter.on('complete', () => events.push('complete'));
|
|
387
|
+
|
|
388
|
+
await performAsyncOperation(emitter);
|
|
389
|
+
|
|
390
|
+
expect(events).toEqual(['start', 'process', 'complete']);
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Pattern 3: Testing Retry Logic
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
test('should retry failed operations', async () => {
|
|
398
|
+
let attempts = 0;
|
|
399
|
+
const unreliableOperation = async () => {
|
|
400
|
+
attempts++;
|
|
401
|
+
if (attempts < 3) throw new Error('Temporary failure');
|
|
402
|
+
return 'success';
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const result = await retryOperation(unreliableOperation, 3);
|
|
406
|
+
|
|
407
|
+
expect(result).toBe('success');
|
|
408
|
+
expect(attempts).toBe(3);
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Pattern 4: Testing Debouncing/Throttling
|
|
413
|
+
|
|
414
|
+
```javascript
|
|
415
|
+
test('should debounce rapid calls', async () => {
|
|
416
|
+
let callCount = 0;
|
|
417
|
+
const debouncedFn = debounce(() => callCount++, 100);
|
|
418
|
+
|
|
419
|
+
// Rapid calls
|
|
420
|
+
debouncedFn();
|
|
421
|
+
debouncedFn();
|
|
422
|
+
debouncedFn();
|
|
423
|
+
|
|
424
|
+
// Wait for debounce period
|
|
425
|
+
await delay(150);
|
|
426
|
+
|
|
427
|
+
expect(callCount).toBe(1); // Only called once
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Testing Async State Management
|
|
432
|
+
|
|
433
|
+
### Testing Loading States
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
test('should show loading state during async operation', async () => {
|
|
437
|
+
const component = render(<AsyncComponent />);
|
|
438
|
+
|
|
439
|
+
// Initial state
|
|
440
|
+
expect(component.getByText('Loading...')).toBeInTheDocument();
|
|
441
|
+
|
|
442
|
+
// Wait for async operation
|
|
443
|
+
await waitFor(() => {
|
|
444
|
+
expect(component.getByText('Data loaded')).toBeInTheDocument();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
expect(component.queryByText('Loading...')).not.toBeInTheDocument();
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Testing Error States
|
|
452
|
+
|
|
453
|
+
```javascript
|
|
454
|
+
test('should show error state on failure', async () => {
|
|
455
|
+
// Mock API to fail
|
|
456
|
+
mockApi.fetchData.mockRejectedValue(new Error('Network error'));
|
|
457
|
+
|
|
458
|
+
const component = render(<AsyncComponent />);
|
|
459
|
+
|
|
460
|
+
await waitFor(() => {
|
|
461
|
+
expect(component.getByText('Error: Network error')).toBeInTheDocument();
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Best Practices
|
|
467
|
+
|
|
468
|
+
### ✅ DO: Control Time in Tests
|
|
469
|
+
|
|
470
|
+
```javascript
|
|
471
|
+
// Good: Mock timers for deterministic tests
|
|
472
|
+
jest.useFakeTimers();
|
|
473
|
+
|
|
474
|
+
test('should execute after delay', () => {
|
|
475
|
+
const callback = jest.fn();
|
|
476
|
+
setTimeout(callback, 1000);
|
|
477
|
+
|
|
478
|
+
jest.advanceTimersByTime(1000);
|
|
479
|
+
|
|
480
|
+
expect(callback).toHaveBeenCalled();
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### ✅ DO: Test Both Success and Failure Paths
|
|
485
|
+
|
|
486
|
+
```python
|
|
487
|
+
@pytest.mark.asyncio
|
|
488
|
+
async def test_success_path():
|
|
489
|
+
result = await operation_that_succeeds()
|
|
490
|
+
assert result.is_success()
|
|
491
|
+
|
|
492
|
+
@pytest.mark.asyncio
|
|
493
|
+
async def test_failure_path():
|
|
494
|
+
with pytest.raises(OperationError):
|
|
495
|
+
await operation_that_fails()
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### ✅ DO: Use Appropriate Timeouts
|
|
499
|
+
|
|
500
|
+
```python
|
|
501
|
+
# Good: Reasonable timeout for test
|
|
502
|
+
@pytest.mark.asyncio
|
|
503
|
+
async def test_with_timeout():
|
|
504
|
+
async with timeout(5): # 5 seconds is reasonable
|
|
505
|
+
result = await long_operation()
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### ❌ DON'T: Use Real Delays in Tests
|
|
509
|
+
|
|
510
|
+
```python
|
|
511
|
+
# Bad: Real delays make tests slow
|
|
512
|
+
await asyncio.sleep(5) # Don't do this!
|
|
513
|
+
|
|
514
|
+
# Good: Mock time or use smaller delays for testing
|
|
515
|
+
with patch('asyncio.sleep'):
|
|
516
|
+
await operation_with_delay()
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### ❌ DON'T: Forget to Await
|
|
520
|
+
|
|
521
|
+
```python
|
|
522
|
+
# Bad: Forgot to await
|
|
523
|
+
def test_async_wrong():
|
|
524
|
+
result = async_function() # Returns coroutine, doesn't execute!
|
|
525
|
+
assert result == expected # Will fail!
|
|
526
|
+
|
|
527
|
+
# Good: Properly await
|
|
528
|
+
@pytest.mark.asyncio
|
|
529
|
+
async def test_async_correct():
|
|
530
|
+
result = await async_function()
|
|
531
|
+
assert result == expected
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### ❌ DON'T: Ignore Unhandled Promise Rejections
|
|
535
|
+
|
|
536
|
+
```javascript
|
|
537
|
+
// Bad: Unhandled rejection
|
|
538
|
+
test('bad test', async () => {
|
|
539
|
+
asyncOperation(); // Promise rejection not handled!
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Good: Handle all promises
|
|
543
|
+
test('good test', async () => {
|
|
544
|
+
await expect(asyncOperation()).rejects.toThrow();
|
|
545
|
+
});
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
## Quick Reference Checklist
|
|
549
|
+
|
|
550
|
+
When testing async code:
|
|
551
|
+
|
|
552
|
+
```
|
|
553
|
+
□ Are all async functions awaited?
|
|
554
|
+
□ Are timeouts reasonable and not using real time?
|
|
555
|
+
□ Are both success and failure paths tested?
|
|
556
|
+
□ Are race conditions tested explicitly?
|
|
557
|
+
□ Are unhandled promise rejections caught?
|
|
558
|
+
□ Are async fixtures cleaned up properly?
|
|
559
|
+
□ Are concurrent operations tested?
|
|
560
|
+
□ Is error handling tested?
|
|
561
|
+
□ Are retry mechanisms tested?
|
|
562
|
+
□ Are loading/error states tested (for UI)?
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
## Remember
|
|
566
|
+
|
|
567
|
+
- **Async code is synchronous in tests** - Use await, don't rely on timing
|
|
568
|
+
- **Mock time** - Never use real delays in tests
|
|
569
|
+
- **Test failure paths** - Errors are part of async operations
|
|
570
|
+
- **Handle all promises** - Unhandled rejections cause flaky tests
|
|
571
|
+
- **Control concurrency** - Test race conditions deliberately
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill_id: code-review
|
|
3
|
+
skill_version: 0.1.0
|
|
4
|
+
description: Systematic approach to reviewing code for quality, correctness, and maintainability.
|
|
5
|
+
updated_at: 2025-10-30T17:00:00Z
|
|
6
|
+
tags: [code-review, quality, collaboration, best-practices]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Code Review
|
|
10
|
+
|
|
11
|
+
Systematic approach to reviewing code for quality, correctness, and maintainability.
|
|
12
|
+
|
|
13
|
+
## Code Review Checklist
|
|
14
|
+
|
|
15
|
+
### Correctness
|
|
16
|
+
```
|
|
17
|
+
□ Logic is correct and handles edge cases
|
|
18
|
+
□ Error handling is appropriate
|
|
19
|
+
□ No obvious bugs or issues
|
|
20
|
+
□ Test coverage is adequate
|
|
21
|
+
□ Code works as intended
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Design & Architecture
|
|
25
|
+
```
|
|
26
|
+
□ Follows SOLID principles
|
|
27
|
+
□ Appropriate design patterns used
|
|
28
|
+
□ No unnecessary complexity
|
|
29
|
+
□ Good separation of concerns
|
|
30
|
+
□ Consistent with existing codebase
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Readability
|
|
34
|
+
```
|
|
35
|
+
□ Clear variable and function names
|
|
36
|
+
□ Functions are small and focused
|
|
37
|
+
□ Comments explain "why" not "what"
|
|
38
|
+
□ Code is self-documenting
|
|
39
|
+
□ Consistent formatting
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Performance
|
|
43
|
+
```
|
|
44
|
+
□ No obvious performance issues
|
|
45
|
+
□ Efficient algorithms used
|
|
46
|
+
□ Appropriate data structures
|
|
47
|
+
□ Database queries optimized
|
|
48
|
+
□ No memory leaks
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Security
|
|
52
|
+
```
|
|
53
|
+
□ Input validation present
|
|
54
|
+
□ No SQL injection vulnerabilities
|
|
55
|
+
□ Authentication/authorization checks
|
|
56
|
+
□ No sensitive data exposed
|
|
57
|
+
□ Dependencies are up to date
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Tests
|
|
61
|
+
```
|
|
62
|
+
□ New code has tests
|
|
63
|
+
□ Tests are meaningful
|
|
64
|
+
□ Edge cases tested
|
|
65
|
+
□ Tests follow AAA pattern
|
|
66
|
+
□ No flaky tests
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Review Comments
|
|
70
|
+
|
|
71
|
+
### Be Constructive
|
|
72
|
+
```
|
|
73
|
+
❌ "This code is terrible"
|
|
74
|
+
✅ "Consider extracting this into a separate function for clarity"
|
|
75
|
+
|
|
76
|
+
❌ "Wrong approach"
|
|
77
|
+
✅ "Have you considered using X pattern? It might simplify this"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Be Specific
|
|
81
|
+
```
|
|
82
|
+
❌ "Improve this"
|
|
83
|
+
✅ "This function is doing too much. Consider splitting into:
|
|
84
|
+
1. Validation function
|
|
85
|
+
2. Processing function
|
|
86
|
+
3. Response builder"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Provide Context
|
|
90
|
+
```
|
|
91
|
+
✅ "This could cause a race condition when multiple requests
|
|
92
|
+
access the cache simultaneously. Consider using a lock:
|
|
93
|
+
|
|
94
|
+
with cache_lock:
|
|
95
|
+
value = cache.get(key)
|
|
96
|
+
"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Common Review Findings
|
|
100
|
+
|
|
101
|
+
### Naming Issues
|
|
102
|
+
```python
|
|
103
|
+
# Poor
|
|
104
|
+
def f(x):
|
|
105
|
+
return x * 2
|
|
106
|
+
|
|
107
|
+
# Better
|
|
108
|
+
def calculate_double(value):
|
|
109
|
+
return value * 2
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Error Handling
|
|
113
|
+
```python
|
|
114
|
+
# Missing error handling
|
|
115
|
+
result = api.call()
|
|
116
|
+
process(result)
|
|
117
|
+
|
|
118
|
+
# With proper handling
|
|
119
|
+
try:
|
|
120
|
+
result = api.call()
|
|
121
|
+
process(result)
|
|
122
|
+
except APIError as e:
|
|
123
|
+
logger.error(f"API call failed: {e}")
|
|
124
|
+
return default_response()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Magic Numbers
|
|
128
|
+
```python
|
|
129
|
+
# Magic numbers
|
|
130
|
+
if user.age > 18 and user.balance > 1000:
|
|
131
|
+
|
|
132
|
+
# Named constants
|
|
133
|
+
MIN_AGE = 18
|
|
134
|
+
MIN_BALANCE = 1000
|
|
135
|
+
if user.age > MIN_AGE and user.balance > MIN_BALANCE:
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Remember
|
|
139
|
+
- Focus on code, not the person
|
|
140
|
+
- Explain the "why" behind suggestions
|
|
141
|
+
- Recognize good code too
|
|
142
|
+
- Suggest, don't demand
|
|
143
|
+
- Pick your battles - not every issue needs fixing
|