# Step 20 — Phase 6.3: 测试 **依赖**: Step 19(构建完成) **参考设计**: §16 ## 目标 实现测试:适配器测试、配置测试、合并逻辑测试、完整流程测试。 ## 文件变更 | # | 文件 | 操作 | 说明 | |---|------|------|------| | 1 | `src/test/fixtures/` | 新建 | 测试用代码样本目录 | | 2 | `src/test/adapter.test.ts` | 新建 | 适配器输出解析测试 | | 3 | `src/test/config.test.ts` | 新建 | 配置读取测试 | | 4 | `src/test/merger.test.ts` | 新建 | 多源结果合并 + 统计计算测试 | | 5 | `src/test/pipeline.test.ts` | 新建 | 全链路集成测试 | | 6 | `src/test/extension.test.ts` | 修改 | 替换占位测试 | --- ## 1. `src/test/fixtures/` 目录 ### `src/test/fixtures/sample.js` ```javascript function test() { var unused = 1; console.log('debug'); return "hello world"; } ``` ### `src/test/fixtures/sample.css` ```css .hello { color: black; background: #FFF; } #test { margin: 0px; } ``` ### `src/test/fixtures/Sample.java` ```java public class Sample { public void test() { String password = "admin123"; System.out.println("debug"); System.out.println("debug"); } } ``` --- ## 2. `src/test/adapter.test.ts` ```typescript import * as assert from 'assert'; import * as vscode from 'vscode'; import { ESLintAdapter } from '../adapters/eslint'; import { StylelintAdapter } from '../adapters/stylelint'; suite('Adapter Tests', () => { test('ESLintAdapter has correct id and languages', () => { const adapter = new ESLintAdapter(); assert.strictEqual(adapter.id, 'eslint'); assert.deepStrictEqual(adapter.supportedLanguages, ['javascript', 'typescript']); }); test('StylelintAdapter has correct id and languages', () => { const adapter = new StylelintAdapter(); assert.strictEqual(adapter.id, 'stylelint'); assert.deepStrictEqual(adapter.supportedLanguages, ['css']); }); test('ESLintAdapter check returns AdapterResult structure', async () => { const adapter = new ESLintAdapter(); if (!adapter.isAvailable()) { return; } const doc = await vscode.workspace.openTextDocument({ content: 'const x = 1;\nconsole.log(x);\n', language: 'javascript', }); const result = await adapter.check(doc, __dirname); assert.ok(result.status === 'ok' || result.status === 'tool-unavailable'); assert.ok(Array.isArray(result.diagnostics)); }); }); ``` --- ## 3. `src/test/config.test.ts` ```typescript import * as assert from 'assert'; import { getAIConfig } from '../config/ai'; import { getLinterConfig } from '../config/linter'; import { getFixerConfig } from '../config/fixer'; suite('Config Tests', () => { test('getAIConfig returns default values', () => { const config = getAIConfig(); assert.strictEqual(config.provider, 'deepseek'); assert.strictEqual(config.model, 'deepseek-chat'); assert.strictEqual(config.temperature, 0.2); assert.strictEqual(config.timeout, 300); assert.strictEqual(config.outputLanguage, 'zh-CN'); }); test('getLinterConfig returns default language map', () => { const config = getLinterConfig(); assert.strictEqual(config.languageMap.javascript, 'eslint'); assert.strictEqual(config.languageMap.java, 'pmd'); assert.strictEqual(config.languageMap.css, 'stylelint'); assert.strictEqual(config.languageMap.jsp, 'jsp'); }); test('getFixerConfig returns default values', () => { const config = getFixerConfig(); assert.strictEqual(config.contextLines, 5); }); }); ``` --- ## 4. `src/test/merger.test.ts` ```typescript import * as assert from 'assert'; import { mergeResults, MergedReport } from '../merger/merger'; import { CustomRuleResult, AIFinding } from '../ai/schema'; import { LinterDiagnostic } from '../types'; suite('Merger Tests', () => { test('mergeResults counts correctly', () => { const staticDiags: LinterDiagnostic[] = [ { severity: 'error', ruleId: 'eslint:no-unused', message: 'x is unused', range: new (require('vscode').Range)(0, 0, 0, 1) }, ]; const customResults: CustomRuleResult[] = [ { ruleId: 'custom:no-console', line: 5, severity: 'warning', message: 'avoid console.log' }, ]; const aiFindings: AIFinding[] = [ { ruleId: 'hardcoded-secret', severity: 'error', category: 'security', title: 'Hardcoded', description: 'Found secret', suggestion: 'Use env', line: 3 }, ]; const report = mergeResults({ staticDiagnostics: staticDiags, customRuleResults: customResults, translatedDiagnostics: [], aiFindings, errors: [], degraded: false, startTime: Date.now(), filePath: '/test/sample.js', language: 'javascript', adapterIds: ['eslint'], }); assert.strictEqual(report.linterCount, 1); assert.strictEqual(report.customRuleCount, 1); assert.strictEqual(report.aiCount, 1); assert.strictEqual(report.degraded, false); assert.strictEqual(report.language, 'javascript'); }); test('mergeResults marks degraded when AI fails', () => { const report = mergeResults({ staticDiagnostics: [], customRuleResults: [], translatedDiagnostics: [], aiFindings: [], errors: ['AI 请求超时'], degraded: true, startTime: Date.now(), filePath: '/test/sample.js', language: 'javascript', adapterIds: ['eslint'], }); assert.strictEqual(report.degraded, true); assert.strictEqual(report.errors.length, 1); }); }); ``` --- ## 5. `src/test/pipeline.test.ts` ```typescript import * as assert from 'assert'; import * as vscode from 'vscode'; import * as path from 'path'; import { ESLintAdapter } from '../adapters/eslint'; import { mergeResults } from '../merger/merger'; suite('Pipeline Tests', () => { test('Full pipeline: linter check + merge', async () => { const adapter = new ESLintAdapter(); if (!adapter.isAvailable()) { return; } const doc = await vscode.workspace.openTextDocument({ content: 'var x = 1;\nvar y = 2;\n', language: 'javascript', }); const staticResult = await adapter.check(doc, __dirname); assert.ok(staticResult.status === 'ok'); const report = mergeResults({ staticDiagnostics: staticResult.diagnostics, customRuleResults: [], translatedDiagnostics: [], aiFindings: [], errors: [], degraded: false, startTime: Date.now(), filePath: 'virtual-doc', language: 'javascript', adapterIds: ['eslint'], }); assert.ok(typeof report.duration === 'number'); assert.ok(typeof report.linterCount === 'number'); assert.strictEqual(report.language, 'javascript'); }); }); ``` --- ## 验证命令 ```bash npm test # compile + lint + test # 或 npm run compile && npm run lint && npm run test ``` --- ## 验收 - [ ] 所有测试文件创建完成 - [ ] 测试夹具文件(fixtures)就位 - [ ] `npm run compile` 通过 - [ ] `npm run lint` 通过 - [ ] `npm test` 通过