Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第十章:开发与调试

本章介绍 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 可用

下一章将介绍扩展开发指南。