第九章:调试与测试
在插件系统的开发过程中,调试和测试是确保系统稳定性和可靠性的关键环节。由于插件系统的复杂性和动态性,传统的调试和测试方法需要进行适配和扩展。本章将详细介绍插件系统的调试技巧和全面的测试策略。
调试策略与工具
1. 插件调试器
javascript
// plugin-debugger.js
class PluginDebugger {
constructor(pluginManager, options = {}) {
this.pluginManager = pluginManager;
this.options = {
logLevel: 'debug',
enableTracing: true,
enableBreakpoints: true,
maxTraceHistory: 1000,
...options
};
this.traceHistory = [];
this.breakpoints = new Map();
this.watchedVariables = new Map();
this.debugSessions = new Map();
this.isDebugging = false;
}
// 启用调试模式
enable() {
if (this.isDebugging) return;
this.isDebugging = true;
this.setupHooks();
this.setupConsoleOverride();
console.log('🐛 Plugin debugger enabled');
}
// 禁用调试模式
disable() {
if (!this.isDebugging) return;
this.isDebugging = false;
this.restoreConsole();
this.clearBreakpoints();
console.log('🐛 Plugin debugger disabled');
}
// 设置调试钩子
setupHooks() {
const hooks = this.pluginManager.hookManager;
// 监听所有钩子执行
const originalHooks = new Map();
for (const [hookName, hook] of hooks.hooks) {
originalHooks.set(hookName, hook.call);
hook.call = (...args) => {
this.traceHookCall(hookName, args);
// 检查断点
if (this.hasBreakpoint(`hook:${hookName}`)) {
this.triggerBreakpoint(`hook:${hookName}`, { hookName, args });
}
try {
const result = originalHooks.get(hookName).apply(hook, args);
this.traceHookResult(hookName, result);
return result;
} catch (error) {
this.traceHookError(hookName, error);
throw error;
}
};
}
}
// 设置控制台重写
setupConsoleOverride() {
this.originalConsole = {
log: console.log,
warn: console.warn,
error: console.error,
debug: console.debug
};
console.log = (...args) => {
this.logWithContext('log', args);
this.originalConsole.log(...args);
};
console.warn = (...args) => {
this.logWithContext('warn', args);
this.originalConsole.warn(...args);
};
console.error = (...args) => {
this.logWithContext('error', args);
this.originalConsole.error(...args);
};
console.debug = (...args) => {
this.logWithContext('debug', args);
if (this.options.logLevel === 'debug') {
this.originalConsole.debug(...args);
}
};
}
// 恢复控制台
restoreConsole() {
if (this.originalConsole) {
Object.assign(console, this.originalConsole);
}
}
// 记录带上下文的日志
logWithContext(level, args) {
const context = this.getCurrentContext();
this.addTrace({
type: 'console',
level,
args,
context,
timestamp: Date.now(),
stack: new Error().stack
});
}
// 获取当前上下文
getCurrentContext() {
const stack = new Error().stack;
const lines = stack.split('\n');
// 查找插件相关的调用栈
for (const line of lines) {
const match = line.match(/at\s+(\w+Plugin)\.(\w+)/);
if (match) {
return {
plugin: match[1],
method: match[2],
line: line.trim()
};
}
}
return { plugin: 'unknown', method: 'unknown', line: 'unknown' };
}
// 添加跟踪记录
addTrace(trace) {
this.traceHistory.push(trace);
// 限制历史记录大小
if (this.traceHistory.length > this.options.maxTraceHistory) {
this.traceHistory.shift();
}
// 检查观察的变量
this.checkWatchedVariables(trace);
}
// 跟踪钩子调用
traceHookCall(hookName, args) {
this.addTrace({
type: 'hook-call',
hookName,
args: this.serializeArgs(args),
timestamp: Date.now()
});
}
// 跟踪钩子结果
traceHookResult(hookName, result) {
this.addTrace({
type: 'hook-result',
hookName,
result: this.serializeArgs([result]),
timestamp: Date.now()
});
}
// 跟踪钩子错误
traceHookError(hookName, error) {
this.addTrace({
type: 'hook-error',
hookName,
error: {
message: error.message,
stack: error.stack
},
timestamp: Date.now()
});
}
// 序列化参数
serializeArgs(args) {
return args.map(arg => {
try {
if (typeof arg === 'function') {
return `[Function: ${arg.name || 'anonymous'}]`;
}
if (typeof arg === 'object' && arg !== null) {
return JSON.stringify(arg, null, 2);
}
return String(arg);
} catch (error) {
return '[Unserializable]';
}
});
}
// 设置断点
setBreakpoint(location, condition = null) {
this.breakpoints.set(location, {
condition,
hitCount: 0,
enabled: true
});
console.log(`🔴 Breakpoint set at: ${location}`);
}
// 移除断点
removeBreakpoint(location) {
if (this.breakpoints.delete(location)) {
console.log(`🟢 Breakpoint removed from: ${location}`);
}
}
// 检查是否有断点
hasBreakpoint(location) {
const breakpoint = this.breakpoints.get(location);
return breakpoint && breakpoint.enabled;
}
// 触发断点
triggerBreakpoint(location, context) {
const breakpoint = this.breakpoints.get(location);
if (!breakpoint) return;
breakpoint.hitCount++;
// 检查条件
if (breakpoint.condition && !this.evaluateCondition(breakpoint.condition, context)) {
return;
}
console.log(`🛑 Breakpoint hit at: ${location}`);
console.log('Context:', context);
// 启动调试会话
this.startDebugSession(location, context);
}
// 评估断点条件
evaluateCondition(condition, context) {
try {
// 简单的条件评估(实际应该更安全)
return new Function('context', `return ${condition}`)(context);
} catch (error) {
console.warn('Breakpoint condition error:', error);
return true;
}
}
// 启动调试会话
startDebugSession(location, context) {
const sessionId = this.generateId();
const session = {
id: sessionId,
location,
context,
startTime: Date.now(),
commands: []
};
this.debugSessions.set(sessionId, session);
// 在实际应用中,这里应该启动交互式调试界面
console.log(`Debug session started: ${sessionId}`);
console.log('Available commands: continue, step, inspect, trace');
}
// 观察变量
watchVariable(name, getter) {
this.watchedVariables.set(name, {
getter,
lastValue: null,
changeCount: 0
});
console.log(`👁️ Watching variable: ${name}`);
}
// 停止观察变量
unwatchVariable(name) {
if (this.watchedVariables.delete(name)) {
console.log(`👁️ Stopped watching variable: ${name}`);
}
}
// 检查观察的变量
checkWatchedVariables(trace) {
for (const [name, watcher] of this.watchedVariables) {
try {
const currentValue = watcher.getter();
if (JSON.stringify(currentValue) !== JSON.stringify(watcher.lastValue)) {
watcher.changeCount++;
console.log(`👁️ Variable changed: ${name}`);
console.log(` Old value:`, watcher.lastValue);
console.log(` New value:`, currentValue);
console.log(` Change count:`, watcher.changeCount);
watcher.lastValue = currentValue;
}
} catch (error) {
console.warn(`Error watching variable ${name}:`, error);
}
}
}
// 获取调试信息
getDebugInfo() {
return {
isDebugging: this.isDebugging,
traceCount: this.traceHistory.length,
breakpoints: Array.from(this.breakpoints.keys()),
watchedVariables: Array.from(this.watchedVariables.keys()),
activeSessions: this.debugSessions.size
};
}
// 导出跟踪历史
exportTrace(filter = {}) {
let traces = this.traceHistory;
// 应用过滤器
if (filter.type) {
traces = traces.filter(t => t.type === filter.type);
}
if (filter.plugin) {
traces = traces.filter(t => t.context?.plugin === filter.plugin);
}
if (filter.since) {
traces = traces.filter(t => t.timestamp >= filter.since);
}
return {
traces,
exportTime: Date.now(),
filter,
totalCount: this.traceHistory.length,
filteredCount: traces.length
};
}
// 清空断点
clearBreakpoints() {
this.breakpoints.clear();
console.log('🟢 All breakpoints cleared');
}
// 生成ID
generateId() {
return Math.random().toString(36).substr(2, 9);
}
}
module.exports = PluginDebugger;
2. 日志系统增强
javascript
// enhanced-logger.js
class EnhancedLogger {
constructor(options = {}) {
this.options = {
level: 'info',
format: 'text',
outputs: ['console'],
includeStack: false,
includeContext: true,
maxLogSize: 1000,
...options
};
this.logs = [];
this.contexts = new Map();
this.filters = [];
this.formatters = new Map();
this.setupFormatters();
}
// 设置格式化器
setupFormatters() {
this.formatters.set('text', this.formatText.bind(this));
this.formatters.set('json', this.formatJSON.bind(this));
this.formatters.set('structured', this.formatStructured.bind(this));
}
// 创建日志条目
createLogEntry(level, message, meta = {}) {
const entry = {
id: this.generateId(),
timestamp: Date.now(),
level,
message,
meta,
context: this.getCurrentContext(),
stack: this.options.includeStack ? new Error().stack : null
};
// 添加到历史
this.logs.push(entry);
// 限制日志大小
if (this.logs.length > this.options.maxLogSize) {
this.logs.shift();
}
return entry;
}
// 获取当前上下文
getCurrentContext() {
if (!this.options.includeContext) return null;
const stack = new Error().stack;
const lines = stack.split('\n');
// 查找调用者信息
for (let i = 3; i < lines.length; i++) {
const line = lines[i];
const match = line.match(/at\s+(?:(\w+)\.)?(\w+)\s+\(([^)]+)\)/);
if (match) {
return {
class: match[1] || null,
method: match[2],
location: match[3]
};
}
}
return null;
}
// 记录日志
log(level, message, meta = {}) {
// 检查日志级别
if (!this.shouldLog(level)) return;
const entry = this.createLogEntry(level, message, meta);
// 应用过滤器
if (!this.passesFilters(entry)) return;
// 输出日志
this.output(entry);
}
// 检查是否应该记录
shouldLog(level) {
const levels = ['debug', 'info', 'warn', 'error'];
const currentIndex = levels.indexOf(this.options.level);
const messageIndex = levels.indexOf(level);
return messageIndex >= currentIndex;
}
// 检查过滤器
passesFilters(entry) {
return this.filters.every(filter => filter(entry));
}
// 输出日志
output(entry) {
const formatter = this.formatters.get(this.options.format);
const formatted = formatter ? formatter(entry) : this.formatText(entry);
this.options.outputs.forEach(output => {
switch (output) {
case 'console':
this.outputToConsole(entry.level, formatted);
break;
case 'file':
this.outputToFile(formatted);
break;
case 'memory':
// 已经存储在 this.logs 中
break;
}
});
}
// 输出到控制台
outputToConsole(level, message) {
const colors = {
debug: '\x1b[36m', // 青色
info: '\x1b[32m', // 绿色
warn: '\x1b[33m', // 黄色
error: '\x1b[31m' // 红色
};
const reset = '\x1b[0m';
const color = colors[level] || '';
console.log(`${color}${message}${reset}`);
}
// 输出到文件
outputToFile(message) {
// 实际应该使用文件系统
console.log(`[FILE] ${message}`);
}
// 文本格式化
formatText(entry) {
const timestamp = new Date(entry.timestamp).toISOString();
const level = entry.level.toUpperCase().padEnd(5);
const context = entry.context ?
`[${entry.context.class || 'Global'}.${entry.context.method}]` : '';
let message = `${timestamp} ${level} ${context} ${entry.message}`;
if (Object.keys(entry.meta).length > 0) {
message += ` ${JSON.stringify(entry.meta)}`;
}
return message;
}
// JSON格式化
formatJSON(entry) {
return JSON.stringify(entry);
}
// 结构化格式化
formatStructured(entry) {
const lines = [
`┌─ ${entry.level.toUpperCase()} [${new Date(entry.timestamp).toISOString()}]`,
`├─ Message: ${entry.message}`
];
if (entry.context) {
lines.push(`├─ Context: ${entry.context.class || 'Global'}.${entry.context.method}`);
}
if (Object.keys(entry.meta).length > 0) {
lines.push(`├─ Meta: ${JSON.stringify(entry.meta, null, 2).replace(/\n/g, '\n│ ')}`);
}
lines.push('└─');
return lines.join('\n');
}
// 便捷方法
debug(message, meta) { this.log('debug', message, meta); }
info(message, meta) { this.log('info', message, meta); }
warn(message, meta) { this.log('warn', message, meta); }
error(message, meta) { this.log('error', message, meta); }
// 添加过滤器
addFilter(filter) {
this.filters.push(filter);
}
// 移除过滤器
removeFilter(filter) {
const index = this.filters.indexOf(filter);
if (index > -1) {
this.filters.splice(index, 1);
}
}
// 搜索日志
search(query) {
const results = this.logs.filter(entry => {
if (typeof query === 'string') {
return entry.message.includes(query) ||
JSON.stringify(entry.meta).includes(query);
}
if (typeof query === 'object') {
return Object.entries(query).every(([key, value]) => {
if (key === 'level') return entry.level === value;
if (key === 'since') return entry.timestamp >= value;
if (key === 'until') return entry.timestamp <= value;
if (key === 'context') return entry.context?.class === value;
return true;
});
}
return false;
});
return results;
}
// 获取统计信息
getStats() {
const stats = {
total: this.logs.length,
byLevel: {},
byContext: {},
timeRange: null
};
if (this.logs.length > 0) {
stats.timeRange = {
start: this.logs[0].timestamp,
end: this.logs[this.logs.length - 1].timestamp
};
this.logs.forEach(entry => {
// 按级别统计
stats.byLevel[entry.level] = (stats.byLevel[entry.level] || 0) + 1;
// 按上下文统计
if (entry.context) {
const contextKey = `${entry.context.class || 'Global'}.${entry.context.method}`;
stats.byContext[contextKey] = (stats.byContext[contextKey] || 0) + 1;
}
});
}
return stats;
}
// 清空日志
clear() {
this.logs = [];
}
// 导出日志
export(format = 'json') {
const formatter = this.formatters.get(format);
if (format === 'json') {
return {
logs: this.logs,
stats: this.getStats(),
exportTime: Date.now()
};
}
return this.logs.map(entry => formatter ? formatter(entry) : this.formatText(entry));
}
// 生成ID
generateId() {
return Math.random().toString(36).substr(2, 9);
}
}
module.exports = EnhancedLogger;
3. 错误追踪系统
javascript
// error-tracker.js
class ErrorTracker {
constructor(options = {}) {
this.options = {
captureUnhandled: true,
captureRejections: true,
maxErrors: 500,
enableSourceMap: false,
...options
};
this.errors = [];
this.errorCounts = new Map();
this.errorPatterns = new Map();
this.handlers = new Set();
if (this.options.captureUnhandled) {
this.setupGlobalHandlers();
}
}
// 设置全局错误处理器
setupGlobalHandlers() {
// 捕获未处理的异常
process.on('uncaughtException', (error) => {
this.captureError(error, {
type: 'uncaughtException',
fatal: true
});
});
// 捕获未处理的Promise拒绝
process.on('unhandledRejection', (reason, promise) => {
this.captureError(reason, {
type: 'unhandledRejection',
promise: promise.toString(),
fatal: false
});
});
}
// 捕获错误
captureError(error, context = {}) {
const errorEntry = {
id: this.generateId(),
timestamp: Date.now(),
message: error.message || String(error),
stack: error.stack || new Error().stack,
type: error.constructor.name,
context,
fingerprint: this.generateFingerprint(error),
count: 1
};
// 检查是否是重复错误
const existing = this.findSimilarError(errorEntry);
if (existing) {
existing.count++;
existing.lastSeen = errorEntry.timestamp;
this.updateErrorCount(existing.fingerprint);
return existing;
}
// 添加新错误
this.errors.push(errorEntry);
this.updateErrorCount(errorEntry.fingerprint);
// 限制错误数量
if (this.errors.length > this.options.maxErrors) {
this.errors.shift();
}
// 通知处理器
this.notifyHandlers(errorEntry);
return errorEntry;
}
// 生成错误指纹
generateFingerprint(error) {
const crypto = require('crypto');
// 使用错误消息和堆栈的前几行生成指纹
const stackLines = (error.stack || '').split('\n').slice(0, 3);
const content = error.message + stackLines.join('\n');
return crypto.createHash('md5').update(content).digest('hex').substring(0, 8);
}
// 查找相似错误
findSimilarError(errorEntry) {
return this.errors.find(existing =>
existing.fingerprint === errorEntry.fingerprint
);
}
// 更新错误计数
updateErrorCount(fingerprint) {
const count = this.errorCounts.get(fingerprint) || 0;
this.errorCounts.set(fingerprint, count + 1);
}
// 通知处理器
notifyHandlers(errorEntry) {
for (const handler of this.handlers) {
try {
handler(errorEntry);
} catch (handlerError) {
console.error('Error in error handler:', handlerError);
}
}
}
// 添加错误处理器
addHandler(handler) {
this.handlers.add(handler);
return () => this.handlers.delete(handler);
}
// 包装函数以捕获错误
wrap(fn, context = {}) {
return async (...args) => {
try {
return await fn(...args);
} catch (error) {
this.captureError(error, {
...context,
function: fn.name,
arguments: args.length
});
throw error;
}
};
}
// 包装插件方法
wrapPlugin(plugin) {
const originalMethods = {};
const methodsToWrap = ['init', 'enable', 'disable', 'destroy'];
methodsToWrap.forEach(methodName => {
if (typeof plugin[methodName] === 'function') {
originalMethods[methodName] = plugin[methodName];
plugin[methodName] = this.wrap(
originalMethods[methodName].bind(plugin),
{
plugin: plugin.name,
method: methodName
}
);
}
});
return () => {
// 恢复原始方法
Object.keys(originalMethods).forEach(methodName => {
plugin[methodName] = originalMethods[methodName];
});
};
}
// 获取错误统计
getStats() {
const now = Date.now();
const oneHour = 60 * 60 * 1000;
const oneDay = 24 * oneHour;
const recentErrors = this.errors.filter(e => now - e.timestamp < oneHour);
const todayErrors = this.errors.filter(e => now - e.timestamp < oneDay);
// 按类型分组
const byType = {};
this.errors.forEach(error => {
byType[error.type] = (byType[error.type] || 0) + error.count;
});
// 最频繁的错误
const topErrors = [...this.errors]
.sort((a, b) => b.count - a.count)
.slice(0, 10)
.map(error => ({
fingerprint: error.fingerprint,
message: error.message,
count: error.count,
type: error.type
}));
return {
total: this.errors.length,
totalOccurrences: this.errors.reduce((sum, e) => sum + e.count, 0),
recentCount: recentErrors.length,
todayCount: todayErrors.length,
byType,
topErrors,
uniqueFingerprints: this.errorCounts.size
};
}
// 搜索错误
search(query) {
return this.errors.filter(error => {
if (typeof query === 'string') {
return error.message.includes(query) ||
error.stack.includes(query);
}
if (typeof query === 'object') {
return Object.entries(query).every(([key, value]) => {
switch (key) {
case 'type':
return error.type === value;
case 'plugin':
return error.context.plugin === value;
case 'since':
return error.timestamp >= value;
case 'fingerprint':
return error.fingerprint === value;
default:
return true;
}
});
}
return false;
});
}
// 获取错误详情
getErrorDetails(fingerprint) {
const errors = this.errors.filter(e => e.fingerprint === fingerprint);
if (errors.length === 0) return null;
const first = errors[0];
const last = errors[errors.length - 1];
return {
fingerprint,
message: first.message,
type: first.type,
stack: first.stack,
firstSeen: first.timestamp,
lastSeen: last.timestamp,
count: errors.reduce((sum, e) => sum + e.count, 0),
contexts: errors.map(e => e.context)
};
}
// 标记错误为已解决
markResolved(fingerprint) {
this.errors.forEach(error => {
if (error.fingerprint === fingerprint) {
error.resolved = true;
error.resolvedAt = Date.now();
}
});
}
// 清理旧错误
cleanup(maxAge = 7 * 24 * 60 * 60 * 1000) { // 7天
const cutoff = Date.now() - maxAge;
const before = this.errors.length;
this.errors = this.errors.filter(error => error.timestamp > cutoff);
const removed = before - this.errors.length;
console.log(`🧹 Cleaned up ${removed} old errors`);
return removed;
}
// 导出错误报告
exportReport() {
return {
summary: this.getStats(),
errors: this.errors.map(error => ({
...error,
stack: error.stack.split('\n').slice(0, 10) // 限制堆栈深度
})),
exportTime: Date.now()
};
}
// 生成ID
generateId() {
return Math.random().toString(36).substr(2, 9);
}
}
module.exports = ErrorTracker;
测试策略
4. 集成测试框架
javascript
// integration-test-framework.js
class IntegrationTestFramework {
constructor() {
this.testSuites = new Map();
this.fixtures = new Map();
this.mocks = new Map();
this.testEnvironments = new Map();
}
// 创建测试环境
createEnvironment(name, setup) {
this.testEnvironments.set(name, setup);
}
// 注册测试夹具
registerFixture(name, fixture) {
this.fixtures.set(name, fixture);
}
// 创建集成测试套件
describe(suiteName, testFn) {
const suite = new IntegrationTestSuite(suiteName, this);
this.testSuites.set(suiteName, suite);
testFn(suite);
return suite;
}
// 运行所有集成测试
async runAll(environment = 'default') {
console.log(`🧪 Running integration tests in ${environment} environment...\n`);
// 设置测试环境
const envSetup = this.testEnvironments.get(environment);
let envContext = {};
if (envSetup) {
envContext = await envSetup();
}
const results = [];
for (const [suiteName, suite] of this.testSuites) {
console.log(`Testing ${suiteName}:`);
const suiteResult = await this.runSuite(suite, envContext);
results.push(suiteResult);
this.printSuiteResult(suiteResult);
console.log('');
}
// 清理环境
if (envContext.cleanup) {
await envContext.cleanup();
}
this.printSummary(results);
return results;
}
// 运行测试套件
async runSuite(suite, envContext) {
const result = {
suiteName: suite.name,
tests: [],
passed: 0,
failed: 0,
skipped: 0,
duration: 0
};
const startTime = Date.now();
// 运行套件设置
if (suite.setupFn) {
await suite.setupFn(envContext);
}
try {
for (const test of suite.tests) {
const testResult = await this.runIntegrationTest(test, suite, envContext);
result.tests.push(testResult);
if (testResult.status === 'passed') {
result.passed++;
} else if (testResult.status === 'failed') {
result.failed++;
} else {
result.skipped++;
}
}
} finally {
// 运行套件清理
if (suite.teardownFn) {
await suite.teardownFn(envContext);
}
}
result.duration = Date.now() - startTime;
return result;
}
// 运行集成测试
async runIntegrationTest(test, suite, envContext) {
const result = {
name: test.name,
status: 'passed',
error: null,
duration: 0,
artifacts: []
};
const startTime = Date.now();
try {
// 创建测试上下文
const testContext = this.createIntegrationContext(suite, envContext);
// 运行测试前设置
if (suite.beforeEachFn) {
await suite.beforeEachFn(testContext);
}
// 运行测试
await test.fn(testContext);
// 收集测试产物
result.artifacts = testContext.artifacts || [];
} catch (error) {
result.status = 'failed';
result.error = {
message: error.message,
stack: error.stack
};
} finally {
// 运行测试后清理
if (suite.afterEachFn) {
try {
await suite.afterEachFn();
} catch (cleanupError) {
console.warn('Cleanup error:', cleanupError);
}
}
}
result.duration = Date.now() - startTime;
return result;
}
// 创建集成测试上下文
createIntegrationContext(suite, envContext) {
return {
// 环境上下文
...envContext,
// 夹具
fixtures: this.fixtures,
// 断言
assert: this.createAssertAPI(),
// 等待工具
wait: {
forCondition: this.waitForCondition.bind(this),
forTime: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
forEvent: this.waitForEvent.bind(this)
},
// 产物收集
artifacts: [],
addArtifact: function(name, data) {
this.artifacts.push({ name, data, timestamp: Date.now() });
},
// 模拟工具
mock: this.createMockAPI(),
// 插件管理器工厂
createPluginManager: this.createTestPluginManager.bind(this)
};
}
// 创建断言API
createAssertAPI() {
return {
equal: (actual, expected, message) => {
if (actual !== expected) {
throw new Error(message || `Expected ${actual} to equal ${expected}`);
}
},
deepEqual: (actual, expected, message) => {
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
throw new Error(message || `Expected ${JSON.stringify(actual)} to deep equal ${JSON.stringify(expected)}`);
}
},
throws: async (fn, message) => {
let threw = false;
try {
await fn();
} catch {
threw = true;
}
if (!threw) {
throw new Error(message || 'Expected function to throw');
}
},
eventually: async (fn, timeout = 5000, message) => {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
await fn();
return; // 成功
} catch (error) {
if (Date.now() - start >= timeout) {
throw new Error(message || `Condition not met within ${timeout}ms: ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 100));
}
}
}
};
}
// 等待条件
async waitForCondition(condition, timeout = 5000, interval = 100) {
const start = Date.now();
while (Date.now() - start < timeout) {
if (await condition()) {
return true;
}
await new Promise(resolve => setTimeout(resolve, interval));
}
throw new Error(`Condition not met within ${timeout}ms`);
}
// 等待事件
async waitForEvent(emitter, event, timeout = 5000) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Event ${event} not emitted within ${timeout}ms`));
}, timeout);
emitter.once(event, (data) => {
clearTimeout(timer);
resolve(data);
});
});
}
// 创建模拟API
createMockAPI() {
return {
service: (name, implementation) => {
this.mocks.set(name, implementation);
},
plugin: (name, methods = {}) => {
return {
name,
version: '1.0.0-test',
...methods
};
},
clear: () => {
this.mocks.clear();
}
};
}
// 创建测试插件管理器
createTestPluginManager(options = {}) {
const OptimizedPluginManager = require('../chapter-08/optimized-plugin-manager');
return new OptimizedPluginManager({
...options,
// 测试环境配置
cache: { maxSize: 10, ttl: 60000 },
memory: { threshold: 0.9, interval: 60000 }
});
}
// 打印套件结果
printSuiteResult(result) {
result.tests.forEach(test => {
const status = test.status === 'passed' ? '✅' : '❌';
const duration = `(${test.duration}ms)`;
console.log(` ${status} ${test.name} ${duration}`);
if (test.error) {
console.log(` Error: ${test.error.message}`);
}
if (test.artifacts.length > 0) {
console.log(` Artifacts: ${test.artifacts.length}`);
}
});
const summary = `${result.passed} passed, ${result.failed} failed, ${result.skipped} skipped`;
console.log(` Summary: ${summary} (${result.duration}ms)`);
}
// 打印总结
printSummary(results) {
const totalPassed = results.reduce((sum, r) => sum + r.passed, 0);
const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);
const totalSkipped = results.reduce((sum, r) => sum + r.skipped, 0);
const totalDuration = results.reduce((sum, r) => sum + r.duration, 0);
console.log('📊 Integration Test Summary:');
console.log(` Total: ${totalPassed + totalFailed + totalSkipped} tests`);
console.log(` Passed: ${totalPassed}`);
console.log(` Failed: ${totalFailed}`);
console.log(` Skipped: ${totalSkipped}`);
console.log(` Duration: ${totalDuration}ms`);
if (totalFailed > 0) {
console.log('\n❌ Some integration tests failed!');
} else {
console.log('\n✅ All integration tests passed!');
}
}
}
class IntegrationTestSuite {
constructor(name, framework) {
this.name = name;
this.framework = framework;
this.tests = [];
this.setupFn = null;
this.teardownFn = null;
this.beforeEachFn = null;
this.afterEachFn = null;
}
// 添加测试
it(name, testFn) {
this.tests.push({ name, fn: testFn });
}
// 设置套件
setup(fn) {
this.setupFn = fn;
}
// 清理套件
teardown(fn) {
this.teardownFn = fn;
}
// 每个测试前执行
beforeEach(fn) {
this.beforeEachFn = fn;
}
// 每个测试后执行
afterEach(fn) {
this.afterEachFn = fn;
}
}
module.exports = IntegrationTestFramework;
实际应用示例
让我们创建一个完整的调试和测试示例:
调试示例
javascript
// debug-example.js
const OptimizedPluginManager = require('../chapter-08/optimized-plugin-manager');
const PluginDebugger = require('./plugin-debugger');
const EnhancedLogger = require('./enhanced-logger');
const ErrorTracker = require('./error-tracker');
// 创建一个有问题的插件用于调试
class BuggyPlugin {
constructor() {
this.name = 'buggy';
this.version = '1.0.0';
this.counter = 0;
}
async init(context) {
this.context = context;
// 故意引入一个bug
if (Math.random() < 0.3) {
throw new Error('Random initialization error');
}
// 注册一个有问题的服务
context.services.register('buggyService', () => ({
doSomething: () => this.doSomething(),
getCounter: () => this.counter
}));
context.log('Buggy plugin initialized');
}
doSomething() {
this.counter++;
// 另一个随机bug
if (this.counter > 5 && Math.random() < 0.5) {
throw new Error(`Counter overflow at ${this.counter}`);
}
return `Did something ${this.counter} times`;
}
async enable() {
// 模拟异步操作中的错误
await new Promise(resolve => setTimeout(resolve, 100));
if (this.counter > 3) {
throw new Error('Cannot enable when counter > 3');
}
this.context.log('Buggy plugin enabled');
}
}
async function runDebugExample() {
console.log('🐛 Running debug example...\n');
// 创建增强的日志器
const logger = new EnhancedLogger({
level: 'debug',
format: 'structured',
includeContext: true,
includeStack: false
});
// 创建错误追踪器
const errorTracker = new ErrorTracker({
captureUnhandled: true,
captureRejections: true
});
// 创建插件管理器
const pluginManager = new OptimizedPluginManager();
// 创建调试器
const debugger = new PluginDebugger(pluginManager, {
logLevel: 'debug',
enableTracing: true,
enableBreakpoints: true
});
try {
// 启用调试
debugger.enable();
// 设置错误处理器
errorTracker.addHandler((error) => {
logger.error('Captured error', {
fingerprint: error.fingerprint,
type: error.type,
context: error.context
});
});
// 设置断点
debugger.setBreakpoint('hook:beforeInit');
debugger.setBreakpoint('plugin:buggy:init');
// 观察变量
debugger.watchVariable('pluginCount', () => pluginManager.plugins.size);
console.log('🔧 Setting up plugins with debugging...');
// 注册多个插件,包括有问题的插件
const plugins = [
new BuggyPlugin(),
new BuggyPlugin(), // 第二个实例
];
for (const plugin of plugins) {
try {
// 包装插件以捕获错误
errorTracker.wrapPlugin(plugin);
await pluginManager.register(plugin);
logger.info('Plugin registered', { plugin: plugin.name });
} catch (error) {
logger.error('Plugin registration failed', {
plugin: plugin.name,
error: error.message
});
}
}
console.log('\n🚀 Initializing plugins...');
// 尝试初始化插件
for (const pluginName of pluginManager.plugins.keys()) {
try {
await pluginManager.initPlugin(pluginName);
logger.info('Plugin initialized', { plugin: pluginName });
} catch (error) {
logger.error('Plugin initialization failed', {
plugin: pluginName,
error: error.message
});
}
}
console.log('\n⚡ Enabling plugins...');
// 尝试启用插件
for (const pluginName of pluginManager.plugins.keys()) {
try {
await pluginManager.enablePlugin(pluginName);
logger.info('Plugin enabled', { plugin: pluginName });
} catch (error) {
logger.error('Plugin enable failed', {
plugin: pluginName,
error: error.message
});
}
}
console.log('\n🧪 Testing plugin functionality...');
// 测试插件功能
for (let i = 0; i < 10; i++) {
try {
const service = pluginManager.context.communication.serviceRegistry.get('buggy.buggyService');
if (service) {
const result = service.doSomething();
logger.debug('Service call result', { result, iteration: i });
}
} catch (error) {
logger.warn('Service call failed', {
iteration: i,
error: error.message
});
}
// 短暂延迟
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log('\n📊 Debug Information:');
console.log('='.repeat(50));
// 显示调试信息
const debugInfo = debugger.getDebugInfo();
console.log('Debugger status:', debugInfo);
// 显示错误统计
const errorStats = errorTracker.getStats();
console.log('\nError statistics:', errorStats);
// 显示日志统计
const logStats = logger.getStats();
console.log('\nLog statistics:', logStats);
// 导出跟踪信息
console.log('\n📋 Trace Export:');
const traceExport = debugger.exportTrace({
type: 'hook-call',
since: Date.now() - 60000 // 最近1分钟
});
console.log(`Exported ${traceExport.filteredCount} traces out of ${traceExport.totalCount}`);
// 搜索特定错误
console.log('\n🔍 Error Search:');
const buggyErrors = errorTracker.search({ plugin: 'buggy' });
console.log(`Found ${buggyErrors.length} errors from buggy plugin`);
buggyErrors.forEach(error => {
console.log(` - ${error.message} (${error.count} times)`);
});
} catch (error) {
console.error('❌ Debug example error:', error);
} finally {
// 清理
debugger.disable();
await pluginManager.cleanup();
}
}
// 运行调试示例
if (require.main === module) {
runDebugExample().catch(console.error);
}
module.exports = { runDebugExample };
集成测试示例
javascript
// integration-test-example.js
const IntegrationTestFramework = require('./integration-test-framework');
const OptimizedPluginManager = require('../chapter-08/optimized-plugin-manager');
// 测试插件
class TestPlugin {
constructor(name, options = {}) {
this.name = name;
this.version = '1.0.0';
this.options = options;
this.initialized = false;
this.enabled = false;
}
async init(context) {
if (this.options.initDelay) {
await new Promise(resolve => setTimeout(resolve, this.options.initDelay));
}
if (this.options.shouldFailInit) {
throw new Error(`Init failed for ${this.name}`);
}
this.context = context;
this.initialized = true;
// 注册服务
context.services.register('testService', () => ({
getName: () => this.name,
getStatus: () => ({ initialized: this.initialized, enabled: this.enabled })
}));
}
async enable() {
if (!this.initialized) {
throw new Error('Plugin not initialized');
}
if (this.options.shouldFailEnable) {
throw new Error(`Enable failed for ${this.name}`);
}
this.enabled = true;
}
async disable() {
this.enabled = false;
}
}
async function runIntegrationTests() {
const framework = new IntegrationTestFramework();
// 创建测试环境
framework.createEnvironment('default', async () => {
const pluginManager = new OptimizedPluginManager({
cache: { maxSize: 10, ttl: 60000 },
memory: { threshold: 0.9, interval: 60000 }
});
return {
pluginManager,
cleanup: async () => {
await pluginManager.cleanup();
}
};
});
// 注册测试夹具
framework.registerFixture('basicPlugin', () => new TestPlugin('basic'));
framework.registerFixture('slowPlugin', () => new TestPlugin('slow', { initDelay: 500 }));
framework.registerFixture('faultyPlugin', () => new TestPlugin('faulty', { shouldFailInit: true }));
// 插件注册测试
framework.describe('Plugin Registration', (suite) => {
suite.it('should register a basic plugin', async (context) => {
const plugin = context.fixtures.get('basicPlugin')();
await context.pluginManager.register(plugin);
const registered = context.pluginManager.getPlugin('basic');
context.assert.equal(registered.name, 'basic');
});
suite.it('should handle duplicate registration', async (context) => {
const plugin1 = context.fixtures.get('basicPlugin')();
const plugin2 = context.fixtures.get('basicPlugin')();
await context.pluginManager.register(plugin1);
await context.assert.throws(async () => {
await context.pluginManager.register(plugin2);
}, 'Should throw on duplicate registration');
});
suite.it('should register multiple plugins', async (context) => {
const plugins = [
context.fixtures.get('basicPlugin')(),
new TestPlugin('another')
];
for (const plugin of plugins) {
await context.pluginManager.register(plugin);
}
context.assert.equal(context.pluginManager.plugins.size, 2);
});
});
// 插件生命周期测试
framework.describe('Plugin Lifecycle', (suite) => {
let pluginManager;
suite.setup(async (context) => {
pluginManager = context.pluginManager;
});
suite.beforeEach(async (context) => {
// 为每个测试准备新的插件
const plugin = context.fixtures.get('basicPlugin')();
await pluginManager.register(plugin);
context.testPlugin = plugin;
});
suite.it('should initialize plugin', async (context) => {
await pluginManager.initPlugin('basic');
const plugin = pluginManager.getPlugin('basic');
context.assert.equal(plugin.initialized, true);
});
suite.it('should enable initialized plugin', async (context) => {
await pluginManager.initPlugin('basic');
await pluginManager.enablePlugin('basic');
const plugin = pluginManager.getPlugin('basic');
context.assert.equal(plugin.enabled, true);
});
suite.it('should handle initialization failure', async (context) => {
const faultyPlugin = context.fixtures.get('faultyPlugin')();
await pluginManager.register(faultyPlugin);
await context.assert.throws(async () => {
await pluginManager.initPlugin('faulty');
}, 'Should throw on init failure');
});
suite.it('should handle slow initialization', async (context) => {
const slowPlugin = context.fixtures.get('slowPlugin')();
await pluginManager.register(slowPlugin);
const startTime = Date.now();
await pluginManager.initPlugin('slow');
const duration = Date.now() - startTime;
context.assert.equal(duration >= 500, true, 'Should take at least 500ms');
const plugin = pluginManager.getPlugin('slow');
context.assert.equal(plugin.initialized, true);
});
});
// 插件通信测试
framework.describe('Plugin Communication', (suite) => {
suite.setup(async (context) => {
// 注册两个插件用于通信测试
const plugin1 = new TestPlugin('sender');
const plugin2 = new TestPlugin('receiver');
await context.pluginManager.register(plugin1);
await context.pluginManager.register(plugin2);
await context.pluginManager.initAll();
await context.pluginManager.enableAll();
});
suite.it('should allow service communication', async (context) => {
const senderService = context.pluginManager.context.communication.serviceRegistry.get('sender.testService');
const receiverService = context.pluginManager.context.communication.serviceRegistry.get('receiver.testService');
context.assert.equal(senderService.getName(), 'sender');
context.assert.equal(receiverService.getName(), 'receiver');
});
suite.it('should support event communication', async (context) => {
let receivedEvent = null;
// 设置事件监听
context.pluginManager.on('test-event', (data) => {
receivedEvent = data;
});
// 发送事件
context.pluginManager.emit('test-event', { message: 'Hello from test' });
// 等待事件处理
await context.wait.forCondition(() => receivedEvent !== null, 1000);
context.assert.equal(receivedEvent.message, 'Hello from test');
});
suite.it('should handle plugin dependencies', async (context) => {
// 这个测试需要更复杂的依赖插件设置
// 为了简化,我们只测试基本的依赖检查
const plugin = context.pluginManager.getPlugin('sender');
context.assert.equal(plugin !== null, true, 'Sender plugin should exist');
const receiverPlugin = context.pluginManager.getPlugin('receiver');
context.assert.equal(receiverPlugin !== null, true, 'Receiver plugin should exist');
});
});
// 性能测试
framework.describe('Performance Tests', (suite) => {
suite.it('should handle bulk plugin registration', async (context) => {
const pluginCount = 50;
const plugins = [];
// 创建大量插件
for (let i = 0; i < pluginCount; i++) {
plugins.push(new TestPlugin(`bulk-${i}`));
}
const startTime = Date.now();
// 批量注册
for (const plugin of plugins) {
await context.pluginManager.register(plugin);
}
const registrationTime = Date.now() - startTime;
// 批量初始化
const initStartTime = Date.now();
await context.pluginManager.initAll();
const initTime = Date.now() - initStartTime;
// 批量启用
const enableStartTime = Date.now();
await context.pluginManager.enableAll();
const enableTime = Date.now() - enableStartTime;
context.addArtifact('performance-metrics', {
pluginCount,
registrationTime,
initTime,
enableTime,
avgRegistrationTime: registrationTime / pluginCount,
avgInitTime: initTime / pluginCount,
avgEnableTime: enableTime / pluginCount
});
// 验证所有插件都已注册
context.assert.equal(context.pluginManager.plugins.size, pluginCount);
// 性能断言(这些值可能需要根据实际环境调整)
context.assert.equal(registrationTime < 5000, true, 'Registration should complete within 5 seconds');
context.assert.equal(initTime < 10000, true, 'Initialization should complete within 10 seconds');
});
suite.it('should handle concurrent operations', async (context) => {
const concurrentCount = 10;
const plugins = [];
for (let i = 0; i < concurrentCount; i++) {
plugins.push(new TestPlugin(`concurrent-${i}`));
}
// 并发注册
const registrationPromises = plugins.map(plugin =>
context.pluginManager.register(plugin)
);
await Promise.all(registrationPromises);
// 验证所有插件都已注册
context.assert.equal(context.pluginManager.plugins.size, concurrentCount);
// 并发初始化
const initPromises = plugins.map(plugin =>
context.pluginManager.initPlugin(plugin.name)
);
await Promise.all(initPromises);
// 验证所有插件都已初始化
plugins.forEach(plugin => {
context.assert.equal(plugin.initialized, true);
});
});
});
// 运行所有测试
return await framework.runAll('default');
}
// 运行集成测试
if (require.main === module) {
runIntegrationTests().catch(console.error);
}
module.exports = { runIntegrationTests };
综合示例
javascript
// comprehensive-test-example.js
const { runDebugExample } = require('./debug-example');
const { runIntegrationTests } = require('./integration-test-example');
async function runComprehensiveTests() {
console.log('🧪 Running comprehensive plugin system tests...\n');
try {
// 运行调试示例
console.log('='.repeat(60));
console.log('🐛 DEBUG EXAMPLE');
console.log('='.repeat(60));
await runDebugExample();
console.log('\n' + '='.repeat(60));
console.log('🧪 INTEGRATION TESTS');
console.log('='.repeat(60));
// 运行集成测试
const testResults = await runIntegrationTests();
// 分析测试结果
const totalTests = testResults.reduce((sum, suite) =>
sum + suite.passed + suite.failed + suite.skipped, 0);
const totalPassed = testResults.reduce((sum, suite) => sum + suite.passed, 0);
const totalFailed = testResults.reduce((sum, suite) => sum + suite.failed, 0);
console.log('\n' + '='.repeat(60));
console.log('📊 COMPREHENSIVE TEST SUMMARY');
console.log('='.repeat(60));
console.log(`Total Tests: ${totalTests}`);
console.log(`Passed: ${totalPassed}`);
console.log(`Failed: ${totalFailed}`);
console.log(`Success Rate: ${((totalPassed / totalTests) * 100).toFixed(2)}%`);
if (totalFailed === 0) {
console.log('\n🎉 All tests passed! Plugin system is working correctly.');
} else {
console.log('\n⚠️ Some tests failed. Please review the results above.');
}
} catch (error) {
console.error('❌ Comprehensive test error:', error);
process.exit(1);
}
}
// 运行综合测试
if (require.main === module) {
runComprehensiveTests().catch(console.error);
}
module.exports = { runComprehensiveTests };
小结
在这一章中,我们深入探讨了插件系统的调试和测试策略:
调试工具和技术
- 插件调试器:提供断点、变量观察、调用跟踪等功能
- 增强日志系统:支持多种格式、过滤、搜索和统计
- 错误追踪系统:自动捕获、分类和分析错误
测试策略
- 单元测试:测试单个插件的功能
- 集成测试:测试插件间的交互和系统整体功能
- 性能测试:验证系统在负载下的表现
- 错误处理测试:确保系统能优雅处理异常情况
关键收获
- 全面的调试支持:帮助开发者快速定位和解决问题
- 自动化测试:确保系统的稳定性和可靠性
- 性能监控:及时发现性能瓶颈
- 错误追踪:系统化地管理和解决错误
这些调试和测试工具为插件系统提供了强大的质量保证,确保系统在各种情况下都能稳定运行。
在下一章中,我们将总结整个插件系统教程,并展望未来的发展方向。
练习题
- 实现一个可视化的插件调试界面
- 创建一个自动化的性能回归测试套件
- 设计一个插件兼容性测试框架
下一章预告:我们将总结整个教程,回顾关键概念,并探讨插件系统的未来发展趋势。