Skip to content

测试插件

VS Code 为你的插件提供了运行和调试的能力。测试会运行在一个特殊的 VS Code 实例中——扩展开发环境,这个特殊实例拥有访问 VS Code API 的全部权限。本篇侧重于 VS Code 的集成测试,至于单元测试。

概述

如果你正在使用yo code 生成器,那么生成的项目中应该已经包含了一些测试示例和指引。

使用npm run test或者yarn test启动集成测试,测试工程随后会:

  • 下载并解压最新的 VS Code 版本
  • 运行插件的测试脚本中所规定的Mocha测试

你可以在helloworld-test-sample中找到本篇示例,本篇剩余部分将解释例子中的如下部分:

快速配置:测试 CLI

VS Code 团队发了一个用于测试插件的命令行工具。你可以在插件示例仓库中找到相关示例。

测试 CLI 提供了快速配置能力,你可以很轻松地在 VS Code UI 中使用 Extension Test Runner 来调试测试套件。CLI 底层仅仅使用了 Mocha。

开始之前你需要先安装 @vscode/test-cli,以及 @vscode/test-electron,这样才能在VS Code 桌面环境中运行测试:

bash
npm install --save-dev @vscode/test-cli @vscode/test-electron

安装好这些模块后, 你就可以使用 vscode-test 命令了,然后你可以在package.jsonscripts 部分使用:

json
{
  "name": "my-cool-extension",
  "scripts": {
+   "test": "vscode-test"

vscode-test 会查找当前相对工作路径下的 .vscode-test.js/mjs/cjs 文件。这个文件可以配置测试器,你可以在此查看整个文件的定义。

常用选项如下:

  • (必须) files - 一个规则或者一组规则,或者包含测试套件运行的绝对路径
  • version - 运行测试套件的 VS Code 版本 (默认为 stable 版本).
  • workspaceFolder - 测试套件运行期间可打开的工作区
  • extensionDevelopmentPath - 插件的文件路径 (默认为本配置文件所在目录)
  • mocha - 传递给 Mocha 的对象

示例配置如下:

js
// .vscode-test.js
const { defineConfig } = require('@vscode/test-cli');

module.exports = defineConfig({ files: 'out/test/**/*.test.js' });

进阶可参考:

js
// .vscode-test.js
const { defineConfig } = require('@vscode/test-cli');

module.exports = defineConfig([
  {
    label: 'unitTests',
    files: 'out/test/**/*.test.js',
    version: 'insiders',
    workspaceFolder: './sampleWorkspace',
    mocha: {
      ui: 'tdd',
      timeout: 20000
    }
  }
  // you can specify additional test configurations, too
]);

如果你用数组定义了多个配置,那么 vscode-test 就会按序执行每个测试。你可以用 label 来单独运行测试,传递 --label 标记来运行单个测试,比如 vscode-test --label unitTests。用 vscode-test --help 查看所有吗命令行选项。

测试脚本

CLI 配置好之后,你就编写和运行你自己的测试了。测试脚本可以访问 VS Code API,而且运行在 Mocha 之下。这里有一个例子🌰 src/test/suite/extension.test.ts

你可以用 npm test 命令来运行,或者等你装了 VS Code 的 Extension Test Runner 之后用 Test: Run All Tests 运行测试。你还可以用 Test: Debug All Tests 命令调试测试套件。

ts
import * as assert from 'assert';

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../extension';

suite('Extension Test Suite', () => {
  suiteTeardown(() => {
    vscode.window.showInformationMessage('All tests done!');
  });

  test('Sample test', () => {
    assert.strictEqual(-1, [1, 2, 3].indexOf(5));
    assert.strictEqual(-1, [1, 2, 3].indexOf(0));
  });
});

进阶配置:你自己的测试运行器

本节的配置在 helloworld-test-sample。本节的剩余文档主要解释示例中的以下几个文件:

VS Code 提供了 2 个 CLI 参数来运行插件测试——--extensionDevelopmentPath--extensionTestsPath

例如:

bash
# - Launches VS Code Extension Host
# - Loads the extension at <EXTENSION-ROOT-PATH>
# - Executes the test runner script at <TEST-RUNNER-SCRIPT-PATH>
code \
--extensionDevelopmentPath=<EXTENSION-ROOT-PATH> \
--extensionTestsPath=<TEST-RUNNER-SCRIPT-PATH>

测试入口src/test/runTest.ts)使用了vscode-testAPI,简化了下载、解压、启动 VS Code 的测试流程:

typescript
import * as path from "path";

import { runTests } from "vscode-test";

async function main() {
  try {
    // The folder containing the Extension Manifest package.json
    // Passed to `--extensionDevelopmentPath`
    const extensionDevelopmentPath = path.resolve(__dirname, "../../");

    // The path to the extension test runner script
    // Passed to --extensionTestsPath
    const extensionTestsPath = path.resolve(__dirname, "./suite/index");

    // Download VS Code, unzip it and run the integration test
    await runTests({ extensionDevelopmentPath, extensionTestsPath });
  } catch (err) {
    console.error("Failed to run tests");
    process.exit(1);
  }
}

main();

vscode-test还支持:

  • 启动 VS Code 时打开指定工作区
  • 下载不同版本的 VS Code
  • 使用其他 CLI 参数启动

你可以在microsoft/vscode-test中找到更多用法。

测试脚本

当你运行插件的集成测试时,--extensionTestsPath会指向测试脚本(src/test/suite/index.ts),然后这个脚本会进一步运行测试套件。下面是helloworld-test-sample中的测试脚本,它使用了 Mocha 运行测试套件。你可以把这个文件视为测试的起点,你可以用Mocha 的 API自定义启动时的配置,你也可以用其他任意喜欢的测试框架替代 Mocha。

typescript
import * as path from "path";
import * as Mocha from "mocha";
import * as glob from "glob";

export function run(): Promise<void> {
  // Create the mocha test
  const mocha = new Mocha({
    ui: "tdd"
  });
  mocha.useColors(true);

  const testsRoot = path.resolve(__dirname, "..");

  return new Promise((c, e) => {
    glob("**/**.test.js", { cwd: testsRoot }, (err, files) => {
      if (err) {
        return e(err);
      }

      // Add files to the test suite
      files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));

      try {
        // Run the mocha test
        mocha.run(failures => {
          if (failures > 0) {
            e(new Error(`${failures} tests failed.`));
          } else {
            c();
          }
        });
      } catch (err) {
        e(err);
      }
    });
  });
}

所有测试脚本和*.test.js文件都有访问 VS Code API 的权限。 看看这个例子(src/test/suite/extension.test.ts)

typescript
import * as assert from "assert";
import { after } from "mocha";

// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from "vscode";
// import * as myExtension from '../extension';

suite("Extension Test Suite", () => {
  after(() => {
    vscode.window.showInformationMessage("All tests done!");
  });

  test("Sample test", () => {
    assert.equal(-1, [1, 2, 3].indexOf(5));
    assert.equal(-1, [1, 2, 3].indexOf(0));
  });
});

调试测试文件

调试测试文件和调试插件是一样的,我们看一个launch.json调试器配置的例子:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Extension Tests",
      "type": "extensionHost",
      "request": "launch",
      "runtimeExecutable": "${execPath}",
      "args": [
        "--extensionDevelopmentPath=${workspaceFolder}",
        "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
      ],
      "outFiles": ["${workspaceFolder}/out/test/**/*.js"]
    }
  ]
}

提示

使用 Insider 版本开发插件

由于 VS Code 的限制,如果你使用 VS Code 稳定版来运行集成测试,它会报错:

Running extension tests from the command line is currently only supported if no other instance of Code is running.

所以推荐你使用VS Code Insiders测试插件。

调试时禁用其他插件

当你在 VS Code 中对测试进行调试时,VS Code 使用的是全局安装的 VS Code 实例,它会加载所有插件。你可以在launch.json中添加--disable-extensions或者在runTestslaunchArgs选项中添加该项以禁用其他插件。

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Extension Tests",
      "type": "extensionHost",
      "request": "launch",
      "runtimeExecutable": "${execPath}",
      "args": [
        "--disable-extensions",
        "--extensionDevelopmentPath=${workspaceFolder}",
        "--extensionTestsPath=${workspaceFolder}/out/test/suite/index"
      ],
      "outFiles": ["${workspaceFolder}/out/test/**/*.js"]
    }
  ]
}
typescript
await runTests({
  extensionDevelopmentPath,
  extensionTestsPath,
  /**
   * A list of launch arguments passed to VS Code executable, in addition to `--extensionDevelopmentPath`
   * and `--extensionTestsPath` which are provided by `extensionDevelopmentPath` and `extensionTestsPath`
   * options.
   *
   * If the first argument is a path to a file/folder/workspace, the launched VS Code instance
   * will open it.
   *
   * See `code --help` for possible arguments.
   */
  launchArgs: ["--disable-extensions"]
});

使用@vscode/test-electron自定义配置

你可能会需要自定义一些启动配置,比如启动测试前执行code --install-extension安装一些其他插件这样的场景。@vscode/test-electron提供粒度更细的 API 来操作这样的场景:

typescript
import * as cp from 'child_process';
import * as path from 'path';
import {
  downloadAndUnzipVSCode,
  resolveCliArgsFromVSCodeExecutablePath,
  runTests
} from '@vscode/test-electron';

async function main() {
  try {
    const extensionDevelopmentPath = path.resolve(__dirname, '../../../');
    const extensionTestsPath = path.resolve(__dirname, './suite/index');
    const vscodeExecutablePath = await downloadAndUnzipVSCode('1.40.1');
    const [cliPath, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);

    // Use cp.spawn / cp.exec for custom setup
    cp.spawnSync(
      cliPath,
      [...args, '--install-extension', '<EXTENSION-ID-OR-PATH-TO-VSIX>'],
      {
        encoding: 'utf-8',
        stdio: 'inherit'
      }
    );

    // Run the extension test
    await runTests({
      // Use the specified `code` executable
      vscodeExecutablePath,
      extensionDevelopmentPath,
      extensionTestsPath
    });
  } catch (err) {
    console.error('Failed to run tests');
    process.exit(1);
  }
}

main();

测试工作区信任行为

如果你的插件在 package.json 中声明了 capabilities.untrustedWorkspaces,请为受信任和不受信任的工作区都添加集成测试。你无法从插件测试中以编程方式授予或撤销工作区信任。请对受信任和不受信任的状态分别进行测试运行。

当使用 @vscode/test-cli 时,请定义单独的测试配置,以便你可以独立运行每种信任状态:

  • trustedWorkspaceTests:提供一个不应用信任限制的基线运行。这有助于验证你插件的完整功能行为,并捕获受信任路径中的回归。
  • untrustedWorkspaceTests:在仍启用工作区信任的情况下验证受限模式行为。使用专用的 --user-data-dir 可以防止先前持久化的信任决策使这次运行意外地变为受信任。

因为每个配置都有自己的 label,你可以独立运行它们(例如,vscode-test --label trustedWorkspaceTestsvscode-test --label untrustedWorkspaceTests),或者按顺序运行两者。

js
// .vscode-test.js
const { defineConfig } = require('@vscode/test-cli');
const path = require('path');

module.exports = defineConfig([
  {
    label: 'trustedWorkspaceTests',
    files: 'out/test/**/*.test.js',
    workspaceFolder: './test/fixtures/trusted-workspace',
    // Optional: disables Workspace Trust for this run
    launchArgs: ['--disable-workspace-trust'],
  },
  {
    label: 'untrustedWorkspaceTests',
    files: 'out/test/**/*.test.js',
    workspaceFolder: './test/fixtures/untrusted-workspace',
    // Keep Workspace Trust enabled and isolate user data for deterministic runs
    launchArgs: [
      '--user-data-dir',
      path.join(__dirname, '.vscode-test', 'user-data-untrusted'),
    ],
  },
]);

在你的测试中,通过检查 vscode.workspace.isTrusted 来断言感知信任的行为:

ts
import * as assert from 'assert';
import * as vscode from 'vscode';

suite('Workspace Trust Tests', () => {
  test('extension behavior changes by trust state', async () => {
    const isFeatureAvailable = await vscode.commands.executeCommand<boolean>(
      'myExtension.isRestrictedFeatureEnabled'
    );

    if (vscode.workspace.isTrusted) {
      assert.strictEqual(isFeatureAvailable, true);
    } else {
      assert.strictEqual(isFeatureAvailable, false);
    }
  });
});

关于如何在插件清单插件清单VS Code自定义的pacakge.json文件,其中包含着插件的入口、配置等重要信息。中声明信任要求,以及如何使用 vscode.workspace.isTrusted API 的更多信息,请参阅工作区信任插件指南

Next steps