第十章:开发与调试
本章介绍 VSDB 的开发环境配置、调试流程和测试策略。
10.1 开发环境配置
依赖安装
# 克隆项目
git clone https://github.com/yourname/vsdb.git
cd vsdb
# 安装扩展依赖
npm install --legacy-peer-deps
# 安装 Webview 依赖
cd webview-ui
npm install
cd ..
项目配置
// package.json 关键配置
{
"engines": { "vscode": "^1.85.0" },
"main": "./dist/extension.js",
"scripts": {
"vscode:prepublish": "npm run compile",
"compile": "webpack --mode production",
"watch": "webpack --mode development --watch",
"build:webview": "cd webview-ui && npm run build",
"test": "vitest run",
"lint": "eslint src --ext ts"
}
}
TypeScript 配置
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "ES2020",
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "webview-ui"]
}
10.2 调试配置
VSCode Launch 配置
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
"preLaunchTask": "npm: watch"
},
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/dist/test"
],
"preLaunchTask": "npm: test-compile"
}
]
}
Tasks 配置
// .vscode/tasks.json
{
"version": "2.0.0",
"tasks": [
{
"id": "npm: watch",
"type": "npm",
"script": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true
},
{
"id": "npm: compile",
"type": "npm",
"script": "compile"
}
]
}
调试流程
graph TD
A["F5 启动调试"] --> B["启动 Extension Development Host"]
B --> C["加载扩展代码"]
C --> D["激活扩展"]
D --> E["测试功能"]
E --> F["发现问题"]
F --> G["修改代码"]
G --> H{"代码类型"}
H -->|"扩展代码"| I["自动重编译"]
I --> J["重启扩展 Host"]
H -->|"Webview 代码"| K["手动 rebuild"]
K --> L["刷新 Webview"]
J --> E
L --> E
10.3 Webview 调试
开发模式构建
# Webview 开发模式(支持热重载)
cd webview-ui
npm run dev
Webview DevTools
// 在 Webview Panel 创建时启用 DevTools
const panel = vscode.window.createWebviewPanel(
'vsdb.sqlEditor',
'VSDB SQL Editor',
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
// 启用 DevTools(仅开发时)
enableFindWidget: true,
}
);
// 开发时可以打开 DevTools 检查 React 组件
消息调试
// 在 Webview 端添加调试日志
useEffect(() => {
const handler = (event: MessageEvent) => {
console.log('[Webview] Received:', event.data);
handleExtensionMessage(event.data);
};
window.addEventListener('message', handler);
return () => window.removeEventListener('message', handler);
}, []);
// 在扩展端添加调试日志
panel.webview.onDidReceiveMessage(message => {
console.log('[Extension] Received:', message);
handleWebviewMessage(message);
});
10.4 Worker 调试
Worker 日志
// worker.ts 启用详细日志
console.log('[Worker] Started');
process.on('message', (request: WorkerRequest) => {
console.log('[Worker] Request:', request.type, request.id);
handleMessage(request);
});
function send(message: WorkerResponse) {
console.log('[Worker] Response:', message.type, message.id);
process.send?.(message);
}
IPC 调试
// IpcManager.ts 启用详细日志
sendRequest(request): Promise<WorkerResponse> {
console.log('[IPC] Sending:', request.type, request.id);
return new Promise((resolve, reject) => {
// ...
this.worker.on('message', (message) => {
console.log('[IPC] Received:', message.type, message.id);
this.handleMessage(message);
});
});
}
模拟 Worker 崩溃
// 测试崩溃恢复
// 在 Worker 中手动触发崩溃
process.exit(1);
// 或发送无效消息
send({ type: 'invalid' } as any);
10.5 测试策略
测试架构
graph TD
A["测试层级"] --> B["单元测试"]
A --> C["集成测试"]
A --> D["E2E 测试"]
B --> B1["Parser 测试"]
B --> B2["Manager 测试"]
B --> B3["Driver 测试"]
C --> C1["真实数据库连接"]
C --> C2["IPC 通信测试"]
D --> D1["VSCode Extension Tester"]
D --> D2["UI 交互测试"]
单元测试示例
// __tests__/core/ipcManager.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { IpcManager } from '../../src/core/ipcManager';
describe('IpcManager', () => {
let ipcManager: IpcManager;
beforeEach(() => {
ipcManager = new IpcManager({
workerScriptPath: './dist/worker.js',
requestTimeout: 5000,
});
});
afterEach(async () => {
await ipcManager.shutdown();
});
it('should start worker successfully', async () => {
ipcManager.start();
const ready = await ipcManager.waitReady(5000);
expect(ready).toBe(true);
expect(ipcManager.isRunning()).toBe(true);
});
it('should handle connect request', async () => {
ipcManager.start();
await ipcManager.waitReady();
const response = await ipcManager.sendRequest({
type: 'connect',
connectionId: 'test-conn',
payload: {
config: {
id: 'test-conn',
name: 'test',
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: '',
scope: 'project',
},
},
});
expect(response.type).toBe('result');
});
it('should restart on crash', async () => {
ipcManager.start();
await ipcManager.waitReady();
// 模拟崩溃
const worker = ipcManager.getWorker();
worker?.kill('SIGKILL');
// 等待重启
await new Promise(resolve => setTimeout(resolve, 2000));
expect(ipcManager.isRunning()).toBe(true);
});
});
Parser 测试示例
// __tests__/scanner/envParser.test.ts
import { describe, it, expect } from 'vitest';
import { EnvParser } from '../../src/scanner/envParser';
import * as fs from 'fs';
import * as path from 'path';
describe('EnvParser', () => {
const parser = new EnvParser();
it('should parse DATABASE_URL', async () => {
// 创建临时测试文件
const tempDir = './temp-test-env';
const envFile = path.join(tempDir, '.env');
fs.mkdirSync(tempDir, { recursive: true });
fs.writeFileSync(envFile, 'DATABASE_URL=postgresql://user:pass@localhost:5432/testdb');
const result = await parser.scan(tempDir);
expect(result.connections.length).toBe(1);
expect(result.connections[0].type).toBe('postgresql');
expect(result.connections[0].host).toBe('localhost');
expect(result.connections[0].port).toBe(5432);
// 清理
fs.rmSync(tempDir, { recursive: true });
});
it('should parse MYSQL_* variables', async () => {
const tempDir = './temp-test-mysql';
const envFile = path.join(tempDir, '.env');
fs.mkdirSync(tempDir, { recursive: true });
fs.writeFileSync(envFile, `
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=secret
MYSQL_DATABASE=myapp
`.trim());
const result = await parser.scan(tempDir);
expect(result.connections.length).toBe(1);
expect(result.connections[0].type).toBe('mysql');
expect(result.connections[0].database).toBe('myapp');
fs.rmSync(tempDir, { recursive: true });
});
});
Vitest 配置
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
threshold: {
lines: 80,
functions: 80,
branches: 80,
},
},
testTimeout: 10000,
hookTimeout: 10000,
},
});
10.6 常用调试技巧
日志级别控制
// 根据环境变量控制日志
const DEBUG = process.env.VSDB_DEBUG === 'true';
function log(...args: any[]) {
if (DEBUG) {
console.log('[VSDB]', ...args);
}
}
断点调试
// 在关键位置设置断点
debugger; // VSCode 会在此暂停
// 或使用条件断点
if (request.type === 'query') {
debugger;
}
状态检查
// 定期检查内部状态
setInterval(() => {
console.log('Pending requests:', ipcManager.pendingRequestCount);
console.log('Active connections:', ipcManager.getActiveConnectionIds());
}, 5000);
10.7 小结
本章介绍了 VSDB 的开发与调试:
| 主题 | 内容 |
|---|---|
| 开发环境 | 依赖安装、项目配置 |
| 调试配置 | launch.json、tasks.json |
| Webview 调试 | DevTools、消息日志 |
| Worker 调试 | IPC 日志、崩溃模拟 |
| 测试策略 | 单元测试、集成测试 |
| 测试工具 | Vitest、覆盖率要求 |
关键配置:
- TypeScript strict:严格类型检查
- 覆盖率阈值:80% lines/functions/branches
- 热重载:扩展代码自动重编译
- Webview 调试:React DevTools 可用
下一章将介绍扩展开发指南。