测试与调试
测试和调试是软件开发中不可或缺的环节。传统IDE需要手动编写测试用例、反复运行测试、设置断点、分析日志,过程繁琐且耗时。Claude Code通过AI辅助,让测试和调试变得前所未有的高效。
本文全面介绍如何用Claude Code进行智能化的测试生成、调试分析和性能优化。
运行测试
基础测试运行
最简单的测试运行方式是直接告诉Claude Code:
运行所有的测试
Claude Code会:
- 自动检测项目使用的测试框架(Jest、Mocha、Pytest等)
- 执行测试命令
- 收集并分析测试结果
- 提供清晰的测试报告
I'll run all tests for this project.
🧪 Test Suite: Jest
Running tests...
Test Results:
✓ 124 tests passed
✗ 3 tests failed
⏱️ Total time: 2.3s
Failed Tests:
1. UserController.createUser
- Expected status 201, got 500
- Error: Database connection timeout
2. PostService.getPosts
- Expected array length 5, got 3
- Missing posts with status 'draft'
3. AuthController.login
- Token validation failed
- JWT secret mismatch
Would you like me to investigate and fix these failures?
运行特定测试
针对特定功能的测试:
只运行用户相关的测试
Claude Code会智能匹配相关测试:
Found 15 user-related test files:
Running user tests...
✓ tests/unit/user/UserModel.test.js (8/8 passed)
✓ tests/unit/user/UserValidator.test.js (6/6 passed)
✗ tests/integration/user/UserAPI.test.js (3/5 passed)
✓ tests/e2e/user/userFlow.test.js (4/4 passed)
Summary: 21/24 tests passed
Failed tests in UserAPI.test.js:
- POST /api/users (email uniqueness check)
- GET /api/users/:id (permission check)
- DELETE /api/users/:id (soft delete)
Focus on these 3 failures?
监视模式
让测试在代码变化时自动运行:
以监视模式运行测试,代码修改后自动重新测试
Claude Code会:
npm test -- --watch --coverage
并持续监控测试结果:
Watch mode enabled. Watching for file changes...
[1st run] 124 passed, 3 failed
[App.js changed] Re-running...
[2nd run] 126 passed, 1 failed
[UserService.js changed] Re-running...
[3rd run] 127 passed, 0 failed ✓
All tests passing! Real-time coverage: 87%
测试覆盖率分析
运行测试并生成覆盖率报告
Claude Code会执行完整的覆盖率分析:
Running tests with coverage...
📊 Coverage Report:
File | Lines | Branch | Funcs | Statements
-------------------------+--------+--------+--------+------------
src/
controllers/
UserController.js | 92% | 85% | 100% | 93%
PostController.js | 78% | 70% | 88% | 80%
services/
UserService.js | 95% | 90% | 100% | 96%
PostService.js | 65% | 50% | 75% | 68% ⚠️
models/
User.js | 88% | 80% | 100% | 90%
Post.js | 85% | 75% | 92% | 87%
utils/
validator.js | 100% | 100% | 100% | 100% ✓
Overall Coverage | 86% | 78% | 94% | 87%
⚠️ Low coverage files:
- PostService.js (68% statements)
Missing: edge cases in deletePost, updatePost
Recommendation: Add tests for error scenarios
✓ Excellent coverage:
- validator.js (100%)
- UserService.js (96%)
View detailed HTML report: coverage/lcov-report/index.html
生成测试用例
自动生成单元测试
Claude Code最强大的功能之一是自动生成高质量的测试用例:
为UserService.js生成完整的单元测试
Claude Code会:
- 分析UserService的所有方法
- 识别各种输入场景
- 生成边界条件测试
- 添加错误处理测试
- 包含Mock数据
// tests/services/UserService.test.js (自动生成)
const UserService = require('../../src/services/UserService');
const db = require('../mocks/database');
describe('UserService', () => {
let userService;
beforeEach(() => {
userService = new UserService(db);
jest.clearAllMocks();
});
describe('getUser', () => {
it('should return user when valid ID is provided', async () => {
const mockUser = { id: 1, name: 'John', email: 'john@example.com' };
db.query.mockResolvedValue([mockUser]);
const result = await userService.getUser(1);
expect(result).toEqual(mockUser);
expect(db.query).toHaveBeenCalledWith(
'SELECT * FROM users WHERE id = 1'
);
});
it('should return null when user does not exist', async () => {
db.query.mockResolvedValue([]);
const result = await userService.getUser(999);
expect(result).toBeNull();
});
it('should handle database errors gracefully', async () => {
db.query.mockRejectedValue(new Error('Connection timeout'));
await expect(userService.getUser(1)).rejects.toThrow(
'Connection timeout'
);
});
it('should validate user ID parameter', async () => {
await expect(userService.getUser(-1)).rejects.toThrow(
'Invalid user ID'
);
await expect(userService.getUser('invalid')).rejects.toThrow(
'Invalid user ID'
);
});
});
describe('createUser', () => {
it('should create user with valid data', async () => {
const userData = {
name: 'Jane Doe',
email: 'jane@example.com',
password: 'SecurePass123'
};
db.execute.mockResolvedValue(123);
const userId = await userService.createUser(userData);
expect(userId).toBe(123);
expect(db.execute).toHaveBeenCalledWith(
expect.stringContaining('INSERT INTO users'),
expect.arrayContaining(['Jane Doe', 'jane@example.com'])
);
});
it('should hash password before storing', async () => {
const userData = {
name: 'Jane',
email: 'jane@example.com',
password: 'plaintext'
};
db.execute.mockResolvedValue(123);
await userService.createUser(userData);
const hashedPassword = db.execute.mock.calls[0][2][2];
expect(hashedPassword).not.toBe('plaintext');
expect(hashedPassword.length).toBeGreaterThan(20);
});
it('should validate email format', async () => {
const invalidData = {
name: 'Jane',
email: 'invalid-email',
password: 'Pass123'
};
await expect(userService.createUser(invalidData)).rejects.toThrow(
'Invalid email format'
);
});
it('should enforce password strength', async () => {
const weakPasswords = [
'123',
'password',
' PASS',
'Pass' // less than 6 chars
];
for (const password of weakPasswords) {
await expect(
userService.createUser({
name: 'Jane',
email: 'jane@example.com',
password
})
).rejects.toThrow('Password does not meet requirements');
}
});
it('should handle duplicate email error', async () => {
db.execute.mockRejectedValue(
new Error('Duplicate entry for email')
);
await expect(
userService.createUser({
name: 'Jane',
email: 'existing@example.com',
password: 'Pass123'
})
).rejects.toThrow('Email already registered');
});
});
describe('updateUser', () => {
it('should update user fields', async () => {
const updates = { name: 'Updated Name' };
db.execute.mockResolvedValue(1);
await userService.updateUser(1, updates);
expect(db.execute).toHaveBeenCalledWith(
expect.stringContaining('UPDATE users'),
expect.arrayContaining(['Updated Name', 1])
);
});
it('should not update sensitive fields directly', async () => {
const updates = { password: 'newpass', id: 999 };
await userService.updateUser(1, updates);
// Password should be hashed separately
// ID should not be updatable
expect(db.execute).not.toHaveBeenCalledWith(
expect.stringContaining('SET id ='),
expect.anything()
);
});
});
describe('deleteUser', () => {
it('should soft delete user', async () => {
db.execute.mockResolvedValue(1);
await userService.deleteUser(1);
expect(db.execute).toHaveBeenCalledWith(
expect.stringContaining('UPDATE users SET deleted = 1'),
expect.arrayContaining([1])
);
});
it('should return false if user not found', async () => {
db.execute.mockResolvedValue(0);
const result = await userService.deleteUser(999);
expect(result).toBe(false);
});
});
describe('edge cases', () => {
it('should handle null values', async () => {
await expect(userService.getUser(null)).rejects.toThrow();
await expect(userService.createUser(null)).rejects.toThrow();
});
it('should handle SQL injection attempts', async () => {
const maliciousData = {
name: "'; DROP TABLE users; --",
email: 'hacker@example.com',
password: 'Pass123'
};
// Should use parameterized queries
db.execute.mockResolvedValue(123);
await userService.createUser(maliciousData);
const query = db.execute.mock.calls[0][0];
expect(query).not.toContain("'; DROP TABLE");
});
});
});
生成集成测试
为用户注册API生成集成测试
Claude Code会生成完整的API测试:
// tests/integration/userRegistration.test.js
const request = require('supertest');
const app = require('../../src/app');
const db = require('../../src/db');
const { generateToken } = require('../../src/utils/auth');
describe('User Registration API Integration Tests', () => {
beforeAll(async () => {
await db.migrate.latest();
await db.seed.run();
});
afterAll(async () => {
await db.destroy();
});
describe('POST /api/auth/register', () => {
it('should register new user successfully', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
username: 'newuser',
email: 'newuser@example.com',
password: 'SecurePass123',
confirmPassword: 'SecurePass123'
})
.expect(201);
expect(response.body).toMatchObject({
success: true,
message: 'User registered successfully',
data: {
user: {
id: expect.any(Number),
username: 'newuser',
email: 'newuser@example.com'
},
token: expect.any(String)
}
});
expect(response.body.data.token.length).toBeGreaterThan(50);
});
it('should reject duplicate email', async () => {
const userData = {
username: 'testuser',
email: 'existing@example.com',
password: 'Pass123'
};
// First registration
await request(app)
.post('/api/auth/register')
.send(userData)
.expect(201);
// Duplicate registration
const response = await request(app)
.post('/api/auth/register')
.send(userData)
.expect(409);
expect(response.body).toMatchObject({
success: false,
error: 'Email already registered'
});
});
it('should validate password confirmation', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
username: 'testuser',
email: 'test@example.com',
password: 'Pass123',
confirmPassword: 'DifferentPass'
})
.expect(400);
expect(response.body).toMatchObject({
success: false,
error: 'Passwords do not match'
});
});
it('should validate required fields', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
username: 'testuser'
// missing email and password
})
.expect(400);
expect(response.body.error).toMatch(/required/);
});
it('should send welcome email after registration', async () => {
const emailService = require('../../src/services/emailService');
jest.spyOn(emailService, 'sendWelcomeEmail');
await request(app)
.post('/api/auth/register')
.send({
username: 'newuser',
email: 'welcome@example.com',
password: 'Pass123',
confirmPassword: 'Pass123'
})
.expect(201);
expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith(
'welcome@example.com',
'newuser'
);
});
it('should rate limit registration attempts', async () => {
const attempts = Array(6).fill(null).map(() =>
request(app)
.post('/api/auth/register')
.send({
username: 'testuser',
email: `test${Math.random()}@example.com`,
password: 'Pass123'
})
);
const responses = await Promise.all(attempts);
// First 5 should succeed, 6th should be rate limited
const successCount = responses.filter(
r => r.status === 201
).length;
const rateLimitedCount = responses.filter(
r => r.status === 429
).length;
expect(successCount).toBe(5);
expect(rateLimitedCount).toBe(1);
});
});
describe('Email Verification Flow', () => {
it('should send verification email on registration', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
username: 'verifyuser',
email: 'verify@example.com',
password: 'Pass123'
})
.expect(201);
// Check database for verification token
const user = await db('users')
.where({ email: 'verify@example.com' })
.first();
expect(user.verification_token).toBeDefined();
expect(user.verified).toBe(false);
});
it('should verify email with valid token', async () => {
const user = await db('users')
.where({ email: 'verify@example.com' })
.first();
await request(app)
.get(`/api/auth/verify-email?token=${user.verification_token}`)
.expect(200);
const verifiedUser = await db('users')
.where({ email: 'verify@example.com' })
.first();
expect(verifiedUser.verified).toBe(true);
});
it('should reject invalid verification token', async () => {
await request(app)
.get('/api/auth/verify-email?token=invalid-token')
.expect(400)
.expect(res => {
expect(res.body.error).toBe('Invalid verification token');
});
});
});
});
生成端到端测试
为用户登录流程生成E2E测试
Claude Code可以生成完整的用户场景测试:
// tests/e2e/loginFlow.test.js
const { chromium } = require('playwright');
const { MongoClient } = require('mongodb');
describe('User Login E2E Tests', () => {
let browser, page, db;
beforeAll(async () => {
browser = await chromium.launch();
db = await MongoClient.connect(process.env.MONGO_URI);
});
afterAll(async () => {
await browser.close();
await db.close();
});
beforeEach(async () => {
page = await browser.newPage();
await db.db('test').collection('users').deleteMany({});
});
it('should complete full login flow', async () => {
// 1. Navigate to login page
await page.goto('http://localhost:3000/login');
await expect(page).toHaveTitle(/Login/);
// 2. Enter credentials
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
// 3. Verify redirect to dashboard
await expect(page).toHaveURL('http://localhost:3000/dashboard');
// 4. Verify user is logged in
const username = await page.textContent('[data-testid="user-name"]');
expect(username).toBe('Test User');
// 5. Verify JWT token is stored
const cookies = await page.context().cookies();
const tokenCookie = cookies.find(c => c.name === 'auth_token');
expect(tokenCookie).toBeDefined();
expect(tokenCookie.value.length).toBeGreaterThan(50);
});
it('should show error for invalid credentials', async () => {
await page.goto('http://localhost:3000/login');
await page.fill('[name="email"]', 'wrong@example.com');
await page.fill('[name="password"]', 'wrongpass');
await page.click('button[type="submit"]');
// Should stay on login page
await expect(page).toHaveURL('http://localhost:3000/login');
// Should show error message
const error = await page.textContent('[data-testid="login-error"]');
expect(error).toBe('Invalid email or password');
});
it('should remember me functionality', async () => {
await page.goto('http://localhost:3000/login');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
await page.check('[name="remember"]');
await page.click('button[type="submit"]');
// Check remember me cookie
const cookies = await page.context().cookies();
const rememberMeCookie = cookies.find(
c => c.name === 'remember_me'
);
expect(rememberMeCookie).toBeDefined();
expect(rememberMeCookie.expires).toBeGreaterThan(Date.now() / 1000);
});
it('should redirect authenticated user to dashboard', async () => {
// First login
await page.goto('http://localhost:3000/login');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('http://localhost:3000/dashboard');
// Logout
await page.click('[data-testid="logout-button"]');
// Try to access login page while authenticated
await page.goto('http://localhost:3000/login');
// Should redirect to dashboard
await expect(page).toHaveURL('http://localhost:3000/dashboard');
});
it('should handle password reset flow', async () => {
await page.goto('http://localhost:3000/login');
// Click forgot password
await page.click('a[href="/forgot-password"]');
await expect(page).toHaveURL('http://localhost:3000/forgot-password');
// Enter email
await page.fill('[name="email"]', 'test@example.com');
await page.click('button[type="submit"]');
// Should show success message
const success = await page.textContent('[data-testid="reset-success"]');
expect(success).toContain('Password reset email sent');
// Check database for reset token
const user = await db.db('test').collection('users').findOne({
email: 'test@example.com'
});
expect(user.resetPasswordToken).toBeDefined();
});
});
对比传统方式
传统测试编写方式:
- 手动分析代码逻辑
- 思考需要测试的场景
- 编写测试代码
- 创建Mock数据
- 运行并调试测试
- 修复测试问题
- 耗时: 30分钟-2小时/文件
Claude Code方式:
为OrderController生成完整的测试用例,包括:
- 正常流程
- 错误处理
- 边界条件
- 权限验证
- 性能测试
时间: 1-3分钟,而且测试覆盖更全面。
调试代码
智能错误分析
当代码出现问题时,直接告诉Claude Code:
我的登录功能有问题,用户报告无法登录
Claude Code会:
- 查看相关的代码文件
- 分析可能的问题点
- 检查日志文件
- 提供诊断和建议
I'll investigate the login issue.
🔍 Analyzing login functionality...
Found 3 files related to login:
- src/controllers/AuthController.js
- src/services/AuthService.js
- src/models/User.js
📋 Potential Issues Detected:
1. ⚠️ In AuthController.js (line 45):
- Issue: Comparing password using === instead of bcrypt.compare
- Impact: Passwords will never match (stored as hash, compared as plain text)
- Severity: CRITICAL
Code:
if (user.password === password) { // ❌ Wrong
return { success: true };
}
Should be:
if (await bcrypt.compare(password, user.password)) { // ✓ Correct
return { success: true };
}
2. ⚠️ In AuthService.js (line 23):
- Issue: Missing error handling for database timeouts
- Impact: App crashes when DB is slow
- Severity: HIGH
3. ⚠️ In User model (line 67):
- Issue: Email case sensitivity not handled
- Impact: User@example.com and user@example.com treated as different
- Severity: MEDIUM
Would you like me to fix these issues?
日志分析
Claude Code可以分析日志文件找出问题:
分析logs/error.log中的错误
Claude Code会智能解析日志:
📊 Analyzing logs/error.log...
Log Summary:
- Total errors: 234
- Time range: 2024-01-15 to 2024-01-22
- Most affected endpoint: /api/payments/process
🔥 Top Errors:
1. Database Connection Timeout (127 occurrences)
Pattern: "Error: Connection timeout after 30000ms"
First seen: 2024-01-18 14:23:11
Last seen: 2024-01-22 09:45:33
Most frequent times: 9:00-11:00 AM (peak hours)
Root Cause Analysis:
- Database connection pool size: 10 (too small)
- Query timeout: 30s (too long)
- No connection retry logic
Recommended Fix:
```javascript
// Increase pool size
pool: {
min: 5,
max: 50, // was 10
acquireTimeout: 10000, // was 30000
idle: 5000
}
// Add retry logic
async function queryWithRetry(sql, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await db.query(sql);
} catch (err) {
if (i === retries - 1) throw err;
await sleep(1000 * (i + 1)); // exponential backoff
}
}
}
-
JWT Token Expired (45 occurrences) Pattern: "JsonWebTokenError: jwt expired" Mostly from: mobile app users
Analysis:
- Token expiry: 1 hour (too short for mobile)
- No refresh token mechanism
- No auto-refresh on client
Recommendation:
// Implement refresh tokens
const accessToken = jwt.sign({ userId }, SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ userId }, REFRESH_SECRET, {
expiresIn: '7d'
}); -
Payment Gateway Timeout (38 occurrences) Pattern: "ETIMEDOUT connecting to payment API" Endpoint: POST /api/payments/process
Issues:
- No timeout handling
- No fallback payment gateway
- Transactions left in 'pending' state
Fix:
try {
const result = await paymentGateway.charge(amount);
await updateTransactionStatus(txId, 'completed');
} catch (err) {
if (err.code === 'ETIMEDOUT') {
// Check payment status later
await scheduleStatusCheck(txId);
await updateTransactionStatus(txId, 'pending_confirmation');
}
} -
Unhandled Promise Rejection (12 occurrences) Pattern: "UnhandledPromiseRejectionWarning"
Files affected:
- src/services/NotificationService.js (line 89)
- src/controllers/WebhookController.js (line 45)
Add error handling:
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
📈 Error Trends:
- Daily average: 33 errors/day
- Increasing trend: +15% over last week
- Critical errors need immediate attention
Would you like me to implement these fixes?
### 性能瓶颈分析
分析为什么我的API响应这么慢
Claude Code会进行性能分析:
⏱️ Performance Analysis Report
Testing endpoint: GET /api/posts (with 10000 records)
Current Performance:
- Average response time: 2.3s
- P50: 1.8s
- P95: 4.2s
- P99: 6.1s
- Max: 8.7s
🐌 Performance Bottlenecks Identified:
-
N+1 Query Problem (Impact: 60% of slowness) Location: PostController.getPosts()
Issue: For each post, making separate query to get author info:
// ❌ Bad: N+1 queries
const posts = await db.query('SELECT * FROM posts');
for (const post of posts) {
post.author = await db.query(
'SELECT * FROM users WHERE id = ?',
[post.author_id] // Separate query for each post!
);
}Fix:
// ✓ Good: Single query with JOIN
const posts = await db.query(`
SELECT
posts.*,
users.name as author_name,
users.email as author_email
FROM posts
LEFT JOIN users ON posts.author_id = users.id
`);Expected improvement: 2.3s → 0.4s (82% faster)
-
Missing Index (Impact: 25% of slowness) Location: posts.title, posts.status
Analysis:
- Query filtering by status: 120ms (should be under 5ms)
- Full table scan on every request
Solution:
CREATE INDEX idx_posts_status ON posts(status);
CREATE INDEX idx_posts_created_at ON posts(created_at DESC);
CREATE INDEX idx_posts_author_id ON posts(author_id);Expected improvement: Additional 25% speed boost
-
No Caching (Impact: 10% of slowness) Popular posts queried repeatedly
Solution:
const cacheKey = 'posts:popular';
let posts = await redis.get(cacheKey);
if (!posts) {
posts = await db.query('SELECT * FROM posts ORDER BY views DESC LIMIT 10');
await redis.setex(cacheKey, 300, JSON.stringify(posts)); // Cache for 5 min
}
return JSON.parse(posts); -
Over-fetching Data (Impact: 5% of slowness) Fetching all columns when only need some
Before:
SELECT * FROM posts // Returns 15 columnsAfter:
SELECT id, title, excerpt, author_id, created_at
FROM posts // Only 5 columns
📊 Expected Results After Fixes:
Metric | Before | After | Improvement ----------------+---------+---------+------------- Average | 2.3s | 0.3s | 87% faster P95 | 4.2s | 0.5s | 88% faster P99 | 6.1s | 0.7s | 89% faster Database load | 100% | 15% | 85% reduction Memory usage | 450MB | 120MB | 73% reduction
💡 Additional Recommendations:
- Implement pagination for large datasets
- Add response compression (gzip)
- Use CDN for static assets
- Enable HTTP/2
- Consider read replicas for scaling
Would you like me to implement these optimizations?
### 内存泄漏检测
检查我的应用是否有内存泄漏
Claude Code会分析内存使用模式:
🔍 Memory Leak Detection Report
Monitoring memory usage over 1 hour...
📊 Memory Usage Pattern: Initial: 85 MB After 100 requests: 145 MB After 1000 requests: 380 MB After 10000 requests: 1.2 GB
⚠️ SUSPICIOUS: Memory grows linearly with requests Growth rate: ~110 KB per request This indicates a memory leak!
🐛 Leak Sources Identified:
-
Event Listeners Not Removed (40% of leaks) Location: src/components/ChatWidget.jsx
Issue:
// ❌ Bad: Listeners accumulate
useEffect(() => {
window.addEventListener('resize', handleResize);
window.addEventListener('scroll', handleScroll);
// No cleanup function!
}, []);Fix:
// ✓ Good: Cleanup listeners
useEffect(() => {
window.addEventListener('resize', handleResize);
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('resize', handleResize);
window.removeEventListener('scroll', handleScroll);
};
}, []); -
Unclosed Database Connections (30% of leaks) Location: src/services/DatabaseService.js
Issue:
// ❌ Bad: Connections never closed
async function query(sql) {
const conn = await pool.getConnection();
return conn.query(sql);
// conn.release() never called!
}Fix:
// ✓ Good: Always release
async function query(sql) {
const conn = await pool.getConnection();
try {
return await conn.query(sql);
} finally {
conn.release(); // Always release, even on error
}
} -
Growing Cache Without Eviction (20% of leaks) Location: src/utils/cache.js
Issue:
// ❌ Bad: Infinite cache growth
const cache = new Map();
function set(key, value) {
cache.set(key, value); // Never removes old entries
}Fix:
// ✓ Good: LRU cache with max size
const LRU = require('lru-cache');
const cache = new LRU({
max: 500, // Max 500 entries
ttl: 1000 * 60 * 5 // Expire after 5 min
}); -
Timers Not Cleared (10% of leaks) Location: src/services/PollingService.js
Issue:
// ❌ Bad: Timers accumulate
function startPolling() {
setInterval(() => {
fetchData();
}, 1000);
// Interval ID not saved, can't be cleared
}Fix:
// ✓ Good: Track and clear
const intervals = new Set();
function startPolling() {
const id = setInterval(() => fetchData(), 1000);
intervals.add(id);
}
function stopPolling() {
intervals.forEach(id => clearInterval(id));
intervals.clear();
}
🧪 Verification Steps:
-
Take heap snapshot before:
node --heap-prof snapshot.js -
Run load test:
ab -n 10000 -c 100 http://localhost:3000/api/posts -
Take heap snapshot after
-
Compare snapshots:
Before: 85 MB
After: 95 MB ✓ (acceptable growth)
Would you like me to implement these fixes and verify the memory leaks are resolved?
### 实时调试协助
在开发过程中遇到bug时:
我正在调试这个问题: 当用户点击"保存"按钮时,数据没有保存到数据库
相关文件:
- src/components/SaveButton.js
- src/api/saveData.js
- src/controllers/DataController.js
Claude Code会实时协助调试:
I'll help you debug this save issue. Let me trace through the code flow.
🔍 Debug Session Started
Step 1: Examining SaveButton.js...
Found the click handler:
const handleSave = async () => {
try {
await saveData(formData);
alert('Saved successfully!');
} catch (error) {
console.error('Save failed:', error);
}
};
Issue spotted: ❌
- Error is caught but alert still shows "Saved successfully!"
- Should catch errors properly
Step 2: Examining saveData.js API...
export async function saveData(data) {
const response = await fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
return response.json(); // ⚠️ Doesn't check response.ok
}
Issue spotted: ❌
- Doesn't check if request was successful
- Will return error JSON as if it was success
- No error thrown for non-200 responses
Step 3: Examining DataController.js...
router.post('/api/data', async (req, res) => {
const data = req.body;
// Validation
if (!data.title || data.title.length < 5) {
return res.status(400).json({
error: 'Title must be at least 5 characters'
});
}
try {
const result = await db.save(data);
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
✓ Controller looks correct
🐛 Root Cause Chain:
- Validation fails (title < 5 chars)
- Controller returns 400 error
- saveData() doesn't check response status
- Returns error JSON instead of throwing
- SaveButton shows success alert
💡 Recommended Fixes:
Fix 1: Update saveData.js to handle errors:
export async function saveData(data) {
const response = await fetch('/api/data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || 'Save failed');
}
return response.json();
}
Fix 2: Update SaveButton.js error handling:
const handleSave = async () => {
try {
const result = await saveData(formData);
// Only show success if we get here
alert('Saved successfully!');
onClose();
} catch (error) {
// Show actual error message
alert(`Save failed: ${error.message}`);
console.error('Save failed:', error);
}
};
Fix 3: Add client-side validation:
const handleSave = async () => {
// Validate before sending
if (formData.title.length < 5) {
alert('Title must be at least 5 characters');
return;
}
try {
await saveData(formData);
alert('Saved successfully!');
} catch (error) {
alert(`Save failed: ${error.message}`);
}
};
Would you like me to apply these fixes?
## 错误追踪
### 错误监控集成
帮我集成Sentry进行错误追踪
Claude Code会完整设置错误监控:
I'll set up Sentry error tracking for your project.
📦 Installing Sentry SDK... npm install @sentry/node @sentry/tracing --save
✓ Dependencies installed
🔧 Configuring Sentry...
- Creating sentry.js:
// src/utils/sentry.js
const Sentry = require('@sentry/node');
const { Integrations } = require('@sentry/tracing');
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
integrations: [
new Integrations.Http({ tracing: true }),
new Integrations.Express({ app }),
],
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.2 : 1.0,
beforeSend(event, hint) {
// Filter out sensitive data
if (event.request) {
delete event.request.cookies;
delete event.request.headers;
}
// Filter out specific errors
if (event.exception) {
const error = hint.originalException;
if (error.message && error.message.includes('Unauthorized')) {
// Don't send auth errors to Sentry
return null;
}
}
return event;
},
// Custom context
beforeSend(event) {
event.tags = {
...event.tags,
component: 'backend-api',
version: process.env.APP_VERSION,
};
event.user = {
id: getUserIdFromRequest(),
// Don't send PII
};
return event;
},
});
module.exports = Sentry;
- Adding error handlers:
// src/app.js
const Sentry = require('./utils/sentry');
const express = require('express');
const app = express();
// Sentry request handler
app.use(Sentry.Handlers.requestHandler());
// Your routes
app.use('/api', routes);
// Sentry error handler (must be after routes)
app.use(Sentry.Handlers.errorHandler());
// Global error handler
app.use((err, req, res, next) => {
// Capture in Sentry
Sentry.captureException(err);
// Log error
logger.error('Unhandled error:', err);
// Send response
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal server error'
: err.message
});
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
Sentry.captureException(reason);
logger.error('Unhandled Rejection:', reason);
});
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
Sentry.captureException(error);
logger.error('Uncaught Exception:', error);
process.exit(1); // Exit after logging
});
- Adding performance monitoring:
// Wrap routes for performance tracking
const app = express();
app.use(Sentry.Handlers.tracingHandler());
// Routes will be automatically traced
app.get('/api/posts', async (req, res) => {
const transaction = Sentry.startTransaction({
op: 'database',
name: 'fetch posts',
});
try {
const posts = await db.query('SELECT * FROM posts');
res.json(posts);
} finally {
transaction.finish();
}
});
- Adding custom breadcrumbs:
function logUserAction(action, details) {
Sentry.addBreadcrumb({
category: 'user',
message: action,
level: 'info',
data: details,
});
}
// Usage
app.post('/api/posts', (req, res) => {
logUserAction('create_post', {
post_id: req.body.id,
user_id: req.user.id,
});
// ... rest of code
});
- Creating error boundaries (if using React):
// src/components/ErrorBoundary.jsx
import * as Sentry from '@sentry/react';
class ErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
Sentry.captureException(error, {
contexts: { react: { componentStack: errorInfo.componentStack } },
});
}
render() {
if (this.state.hasError) {
return <ErrorFallback />;
}
return this.props.children;
}
}
// Wrap your app
<Sentry.ErrorBoundary fallback={ErrorFallback}>
<App />
</Sentry.ErrorBoundary>
- Environment setup:
# .env.example
SENTRY_DSN=https://xxxxx@xxxxx.ingest.sentry.io/xxxxx
SENTRY_ENVIRONMENT=production
NODE_ENV=production
✓ Sentry integration complete!
📊 What's being tracked:
Errors:
- Uncaught exceptions ✓
- Unhandled rejections ✓
- HTTP errors ✓
- Custom errors ✓
Performance:
- Request timing ✓
- Database queries ✓
- External API calls ✓
- React renders ✓
Breadcrumbs:
- User actions ✓
- Navigation ✓
- HTTP requests ✓
- Console logs ✓
🔗 View your dashboard: https://sentry.io/organizations/orgname/projects/project/
Next steps:
- Create release:
sentry-cli releases new $VERSION - Upload sourcemaps:
sentry-cli releases files $VERSION upload-sourcemaps - Set up alerts for errors
### 自定义错误处理
创建一个统一的错误处理系统
Claude Code会创建完整的错误处理框架:
```javascript
// src/utils/errors.js
// Base error class
class AppError extends Error {
constructor(message, statusCode, code) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
toJSON() {
return {
error: this.message,
code: this.code,
statusCode: this.statusCode
};
}
}
// Specific error types
class ValidationError extends AppError {
constructor(message, field = null) {
super(message, 400, 'VALIDATION_ERROR');
this.field = field;
}
}
class AuthenticationError extends AppError {
constructor(message = 'Authentication failed') {
super(message, 401, 'AUTHENTICATION_ERROR');
}
}
class AuthorizationError extends AppError {
constructor(message = 'Not authorized') {
super(message, 403, 'AUTHORIZATION_ERROR');
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} not found`, 404, 'NOT_FOUND');
}
}
class ConflictError extends AppError {
constructor(message) {
super(message, 409, 'CONFLICT_ERROR');
}
}
class RateLimitError extends AppError {
constructor(retryAfter = 60) {
super('Too many requests', 429, 'RATE_LIMIT_ERROR');
this.retryAfter = retryAfter;
}
}
class DatabaseError extends AppError {
constructor(message = 'Database operation failed') {
super(message, 500, 'DATABASE_ERROR');
}
}
// Error handler middleware
function errorHandler(err, req, res, next) {
// Log error
logger.error('Error occurred:', {
message: err.message,
code: err.code,
statusCode: err.statusCode,
stack: err.stack,
url: req.url,
method: req.method,
ip: req.ip,
userAgent: req.get('user-agent')
});
// Send to Sentry in production
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(err);
}
// Handle operational errors
if (err.isOperational) {
return res.status(err.statusCode).json(err.toJSON());
}
// Handle programming errors
if (process.env.NODE_ENV === 'production') {
return res.status(500).json({
error: 'Internal server error',
code: 'INTERNAL_ERROR'
});
}
// Development: send full error
res.status(500).json({
error: err.message,
code: 'INTERNAL_ERROR',
stack: err.stack
});
}
// Async error wrapper
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
// 404 handler
function notFoundHandler(req, res) {
throw new NotFoundError(`Route ${req.method} ${req.url}`);
}
module.exports = {
AppError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ConflictError,
RateLimitError,
DatabaseError,
errorHandler,
asyncHandler,
notFoundHandler
};
// Usage example:
// src/controllers/UserController.js
const { ValidationError, NotFoundError, asyncHandler } = require('../utils/errors');
class UserController {
createUser = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// Validation
if (!email || !email.includes('@')) {
throw new ValidationError('Invalid email format', 'email');
}
if (!password || password.length < 6) {
throw new ValidationError('Password too short', 'password');
}
// Check if user exists
const existingUser = await User.findByEmail(email);
if (existingUser) {
throw new ConflictError('Email already registered');
}
// Create user
const user = await User.create({ email, password });
res.status(201).json(user);
});
getUser = asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new NotFoundError('User');
}
res.json(user);
});
}
测试覆盖率提升
覆盖率分析
分析测试覆盖率,找出未测试的代码
Claude Code会深入分析覆盖率:
📊 Test Coverage Analysis
Running tests with coverage...
Current Coverage: 72%
🔍 Uncovered Code Analysis:
1. src/controllers/PaymentController.js (45% covered)
❌ Untested lines:
89-124: processPayment() error handling
156-189: refundPayment() edge cases
201-234: handleWebhook() webhook types
Missing scenarios:
- Payment gateway timeout
- Invalid payment amount
- Refund for non-existent transaction
- Partial refunds
- Webhook signature verification
- Duplicate webhook handling
Recommended tests:
```javascript
describe('Payment error handling', () => {
it('should handle gateway timeout', async () => {
mockGateway.charge.mockRejectedValue(
new Error('ETIMEDOUT')
);
await expect(
controller.processPayment(invalidAmount)
).rejects.toThrow('Payment timeout');
// Verify transaction status
expect(transaction.status).toBe('timeout');
});
it('should handle negative amounts', async () => {
await expect(
controller.processPayment({ amount: -50 })
).rejects.toThrow(
ValidationError,
'Amount must be positive'
);
});
it('should process partial refunds', async () => {
const payment = await createPayment({ amount: 100 });
const refund = await controller.refundPayment({
transactionId: payment.id,
amount: 30 // Partial refund
});
expect(refund.refundAmount).toBe(30);
expect(refund.originalAmount).toBe(100);
});
it('should verify webhook signatures', async () => {
const webhook = {
type: 'payment.succeeded',
signature: 'invalid_signature',
data: { ... }
};
await expect(
controller.handleWebhook(webhook)
).rejects.toThrow('Invalid webhook signature');
});
});
-
src/services/EmailService.js (60% covered)
❌ Untested branches:
- Email sending retry logic
- Template rendering with missing variables
- Attachment handling
- Batch email sending
- Unsubscribe link generation
Critical missing tests:
describe('Email sending retries', () => {
it('should retry on transient failures', async () => {
let attempts = 0;
mockTransporter.sendMail.mockImplementation(() => {
attempts++;
if (attempts < 3) {
throw new Error('ETIMEDOUT');
}
return Promise.resolve({ messageId: '123' });
});
await emailService.send(options);
expect(attempts).toBe(3); // Retried twice
});
it('should give up after max retries', async () => {
mockTransporter.sendMail.mockRejectedValue(
new Error('Permanent failure')
);
await expect(
emailService.send(options)
).rejects.toThrow('Permanent failure');
expect(mockTransporter.sendMail).toHaveBeenCalledTimes(3);
});
}); -
src/utils/validator.js (85% covered)
⚠️ Partial coverage:
- Complex validation rules not tested
- Custom validator functions not covered
Missing edge cases:
describe('Complex validation', () => {
it('should validate nested objects', () => {
const data = {
user: {
profile: {
age: '25', // String, should be number
address: {
zip: '123' // Invalid format
}
}
}
};
const result = validator.validate(data, schema);
expect(result.errors).toHaveLength(2);
});
it('should handle conditional validation', () => {
const schema = {
type: 'object',
if: { properties: { accountType: { const: 'business' } } },
then: {
required: ['taxId', 'companyName']
}
};
const personalAccount = { accountType: 'personal' };
expect(
validator.validate(personalAccount, schema).valid
).toBe(true);
const businessAccount = { accountType: 'business' };
expect(
validator.validate(businessAccount, schema).valid
).toBe(false);
});
}); -
src/middleware/auth.js (50% covered)
❌ Untested scenarios:
- Token expiration edge cases
- Refresh token rotation
- Multi-device logout
- Permission inheritance
- Role hierarchy
Critical tests needed:
describe('Token expiration', () => {
it('should reject expired token', async () => {
const expiredToken = jwt.sign(
{ userId: 123 },
SECRET,
{ expiresIn: '-1h' } // Expired
);
const response = await request(app)
.get('/api/protected')
.set('Authorization', `Bearer ${expiredToken}`)
.expect(401);
expect(response.body.error).toContain('expired');
});
it('should handle token expiration grace period', async () => {
const token = jwt.sign(
{ userId: 123 },
SECRET,
{ expiresIn: '5s' }
);
// Wait for token to expire
await sleep(6000);
// Should still work due to grace period
const response = await request(app)
.get('/api/protected')
.set('Authorization', `Bearer ${token}`)
.expect(200);
});
});
📈 Coverage Improvement Plan:
Priority 1 (Critical):
- Payment error handling: +25% coverage
- Auth token refresh: +15% coverage
- Email retries: +10% coverage
Priority 2 (Important):
- Webhook handling: +12% coverage
- Complex validation: +8% coverage
- Permission checks: +10% coverage
Priority 3 (Nice to have):
- Edge cases: +10% coverage
- Error boundaries: +5% coverage
Target: 72% → 90% (+18%)
Would you like me to generate the missing tests?
### 边界条件测试
为用户注册添加边界条件测试
Claude Code会生成全面的边界测试:
```javascript
// tests/registration/edgeCases.test.js
describe('User Registration - Edge Cases', () => {
describe('Input validation boundaries', () => {
it('should accept minimum valid password length', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'abc123' // Exactly 6 chars
})
.expect(201);
expect(response.body.data.user.id).toBeDefined();
});
it('should reject password just below minimum', async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'abc12' // 5 chars
})
.expect(400);
});
it('should accept maximum valid username length', async () => {
const longUsername = 'a'.repeat(50); // Max length
const response = await request(app)
.post('/api/auth/register')
.send({
username: longUsername,
email: 'test@example.com',
password: 'password123'
})
.expect(201);
});
it('should reject username exceeding maximum', async () => {
const tooLongUsername = 'a'.repeat(51);
await request(app)
.post('/api/auth/register')
.send({
username: tooLongUsername,
email: 'test@example.com',
password: 'password123'
})
.expect(400);
});
it('should handle empty strings', async () => {
await request(app)
.post('/api/auth/register')
.send({
username: '', // Empty
email: 'test@example.com',
password: 'password123'
})
.expect(400);
});
it('should handle whitespace-only values', async () => {
await request(app)
.post('/api/auth/register')
.send({
username: ' ', // Only spaces
email: 'test@example.com',
password: 'password123'
})
.expect(400);
});
});
describe('Email format edge cases', () => {
const invalidEmails = [
'plainaddress',
'@missingusername.com',
'username@',
'username@.com',
'username@domain.',
'.username@yahoo.com',
'username@yahoo.com.',
'username@yahoo..com',
'username@yahoo@.com',
'username@yahoo.c',
'username@yahoo.corporate'
];
it.each(invalidEmails)('should reject invalid email: %s', async (email) => {
await request(app)
.post('/api/auth/register')
.send({
email,
password: 'password123'
})
.expect(400);
});
it('should handle email with special characters', async () => {
const validSpecialChars = [
'user+tag@example.com',
'user.name@example.com',
'user_name@example.com',
'user-name@example.com'
];
for (const email of validSpecialChars) {
await request(app)
.post('/api/auth/register')
.send({
email,
password: 'password123'
})
.expect(201);
}
});
it('should handle international email addresses', async () => {
const internationalEmails = [
'用户@例子.广告',
'उपयोगकर्ता@उदाहरण.परीक्षा',
'user@example.中国'
];
for (const email of internationalEmails) {
await request(app)
.post('/api/auth/register')
.send({
email,
password: 'password123'
})
.expect(201);
}
});
});
describe('Password complexity edge cases', () => {
it('should enforce uppercase requirement', async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'lowercase123'
})
.expect(400);
});
it('should enforce lowercase requirement', async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'UPPERCASE123'
})
.expect(400);
});
it('should enforce number requirement', async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'NoNumbers'
})
.expect(400);
});
it('should handle password with only special chars', async () => {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: '!@#$%^&*()'
})
.expect(400);
});
it('should handle password with common patterns', async () => {
const commonPasswords = [
'Password123',
'12345678',
'qwerty123',
'admin123',
'letmein123'
];
for (const password of commonPasswords) {
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password
})
.expect(400); // Should reject common passwords
}
});
});
describe('Concurrent registration edge cases', () => {
it('should handle simultaneous duplicate registrations', async () => {
const userData = {
email: 'concurrent@example.com',
password: 'Password123'
};
// Send 10 identical requests simultaneously
const requests = Array(10).fill(null).map(() =>
request(app)
.post('/api/auth/register')
.send(userData)
);
const responses = await Promise.all(requests);
// Only one should succeed
const successes = responses.filter(r => r.status === 201);
const conflicts = responses.filter(r => r.status === 409);
expect(successes).toHaveLength(1);
expect(conflicts).toHaveLength(9);
});
it('should handle rapid sequential registrations', async () => {
const requests = [];
// Send 100 requests as fast as possible
for (let i = 0; i < 100; i++) {
requests.push(
request(app)
.post('/api/auth/register')
.send({
email: `user${i}@example.com`,
password: 'Password123'
})
);
}
const responses = await Promise.all(requests);
const successes = responses.filter(r => r.status === 201);
// All should succeed (different emails)
expect(successes).toHaveLength(100);
});
});
describe('Database edge cases', () => {
it('should handle database connection loss', async () => {
// Simulate DB disconnect
await db.disconnect();
const response = await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'Password123'
})
.expect(503);
expect(response.body.error).toContain('Database unavailable');
// Reconnect
await db.connect();
});
it('should handle transaction rollback on error', async () => {
// Mock DB error in middle of transaction
db.transaction.mockImplementationOnce(async (callback) => {
await callback();
throw new Error('Transaction failed');
});
await request(app)
.post('/api/auth/register')
.send({
email: 'test@example.com',
password: 'Password123'
})
.expect(500);
// Verify user was not created
const user = await db.users.findOne({ email: 'test@example.com' });
expect(user).toBeNull();
});
});
describe('Security edge cases', () => {
it('should sanitize HTML in username', async () => {
const response = await request(app)
.post('/api/auth/register')
.send({
username: '<script>alert("xss")</script>',
email: 'test@example.com',
password: 'Password123'
})
.expect(201);
// Should be escaped
expect(response.body.data.user.username).not.toContain('<script>');
});
it('should prevent SQL injection in email', async () => {
const maliciousEmail = "'; DROP TABLE users; --";
await request(app)
.post('/api/auth/register')
.send({
email: maliciousEmail,
password: 'Password123'
})
.expect(400); // Should be rejected by validation
// Verify users table still exists
const users = await db.users.find();
expect(Array.isArray(users)).toBe(true);
});
it('should rate limit repeated failed attempts', async () => {
const requests = Array(20).fill(null).map(() =>
request(app)
.post('/api/auth/register')
.send({
email: 'existing@example.com', // Will fail (duplicate)
password: 'Password123'
})
);
const responses = await Promise.all(requests);
const rateLimited = responses.filter(r => r.status === 429);
expect(rateLimited.length).toBeGreaterThan(0);
});
});
});
实用技巧和最佳实践
测试技巧
1. 使用测试工厂模式
// tests/factories/userFactory.js
class UserFactory {
static create(overrides = {}) {
return {
username: faker.internet.userName(),
email: faker.internet.email(),
password: 'SecurePass123',
role: 'user',
...overrides
};
}
static async createInDb(overrides = {}) {
const userData = this.create(overrides);
return await User.create(userData);
}
}
// Usage in tests
const user = await UserFactory.createInDb({ role: 'admin' });
2. 使用固定装置(Fixtures)
// tests/fixtures/users.json
{
"standardUser": {
"email": "user@example.com",
"password": "Password123",
"role": "user"
},
"adminUser": {
"email": "admin@example.com",
"password": "AdminPass123",
"role": "admin"
}
}
// Load fixtures in tests
const fixtures = require('../fixtures/users');
const admin = await User.create(fixtures.adminUser);
3. 使用快照测试
it('should match snapshot for user profile', () => {
const profile = generateProfile(user);
expect(profile).toMatchSnapshot();
});
// Update snapshots when API changes intentionally
// npm test -- -u
4. 测试隔离
beforeEach(async () => {
// Clean database before each test
await db.truncateAll();
});
afterEach(async () => {
// Clear mocks
jest.clearAllMocks();
});
调试技巧
1. 使用条件断点
// Instead of:
console.log('Debug:', user);
// Use:
if (user.id === 12345) { // Only log for specific user
console.log('Debug:', user);
}
2. 结构化日志
// Bad
console.log('User created:', user);
// Good
logger.info('User created', {
userId: user.id,
email: user.email,
timestamp: Date.now(),
source: 'UserController.createUser'
});
3. 错误堆栈追踪
function enhanceError(error, context) {
error.context = context;
error.timestamp = new Date().toISOString();
Error.captureStackTrace(error, enhanceError);
return error;
}
try {
await riskyOperation();
} catch (error) {
throw enhanceError(error, {
operation: 'riskyOperation',
userId: req.user.id
});
}
4. 性能监控
function measurePerformance(fn) {
return async (...args) => {
const start = Date.now();
const memoryBefore = process.memoryUsage().heapUsed;
try {
return await fn(...args);
} finally {
const duration = Date.now() - start;
const memoryUsed = process.memoryUsage().heapUsed - memoryBefore;
logger.info('Performance:', {
function: fn.name,
duration: `${duration}ms`,
memory: `${Math.round(memoryUsed / 1024 / 1024)}MB`
});
}
};
}
// Usage
const processData = measurePerformance(async (data) => {
// Heavy computation
});
测试与调试工作流
1. 编写测试
↓
2. 运行测试(失败是预期的)
↓
3. 实现功能
↓
4. 运行测试(应该通过)
↓
5. 使用Claude Code分析覆盖率
↓
6. 补充边界测试
↓
7. 性能测试
↓
8. 集成测试
↓
9. 提交代码
常见问题
Q: 测试运行太慢怎么办?
A: Claude Code会建议:
- 使用只测试需要的文件
- 并行运行测试
- 使用Mock替代真实数据库
- 缓存测试结果
Q: 如何测试异步代码?
A: Claude Code会生成正确的异步测试:
// 方式1: 使用async/await
it('should handle async', async () => {
const result = await asyncFunction();
expect(result).toBe('value');
});
// 方式2: 使用done callback
it('should handle callback', (done) => {
asyncCallback((err, result) => {
expect(result).toBe('value');
done();
});
});
// 方式3: 返回Promise
it('should handle promise', () => {
return promiseFunction().then(result => {
expect(result).toBe('value');
});
});
Q: 如何Mock外部依赖?
A: Claude Code会创建完整的Mock:
jest.mock('./api', () => ({
fetchData: jest.fn(() => Promise.resolve({ data: 'mock' }))
}));
// 或者在测试中
const mockFn = jest.fn();
mockFn.mockResolvedValueOnce({ data: 'first' });
mockFn.mockResolvedValueOnce({ data: 'second' });
总结
Claude Code的测试与调试功能彻底改变了传统开发流程:
核心优势:
- 自动生成测试: 从几分钟减少到几秒钟
- 智能错误分析: 快速定位问题根源
- 性能优化建议: 识别瓶颈并提供解决方案
- 覆盖率提升: 全面分析未测试代码
- 边界条件处理: 生成完整的边界测试
与传统方式对比:
| 任务 | 传统方式 | Claude Code | 提升 |
|---|---|---|---|
| 编写单元测试 | 30-60分钟 | 1-3分钟 | 95% |
| 分析错误日志 | 15-30分钟 | 1-2分钟 | 90% |
| 性能分析 | 1-2小时 | 5-10分钟 | 85% |
| 调试内存泄漏 | 2-4小时 | 10-20分钟 | 85% |
| 提高覆盖率 | 几天 | 1-2小时 | 80% |
最佳实践:
- 先写测试,再写代码(TDD)
- 每次提交前运行完整测试套件
- 定期检查测试覆盖率
- 使用Claude Code生成Mock数据
- 保持测试简单独立
- 监控生产环境错误
掌握这些测试与调试技巧,你将能够构建更高质量、更可靠的应用程序,同时大幅提升开发效率。