Files
2026Technology-Competition/docs/superpowers/specs/implementation-steps/20-phase6.3-tests.md
T

6.9 KiB

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

function test() {
  var unused = 1;
  console.log('debug');
  return "hello world";
}

src/test/fixtures/sample.css

.hello { color: black; background: #FFF; }
#test { margin: 0px; }

src/test/fixtures/Sample.java

public class Sample {
    public void test() {
        String password = "admin123";
        System.out.println("debug");
        System.out.println("debug");
    }
}

2. src/test/adapter.test.ts

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

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

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

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');
  });
});

验证命令

npm test       # compile + lint + test
# 或
npm run compile && npm run lint && npm run test

验收

  • 所有测试文件创建完成
  • 测试夹具文件(fixtures)就位
  • npm run compile 通过
  • npm run lint 通过
  • npm test 通过