测试扩展
Visual Studio Code 支持为你的扩展运行和调试测试。这些测试将在一个名为扩展开发宿主(Extension Development Host)的特殊 VS Code 实例中运行,并拥有对 VS Code API 的完全访问权限。我们将这些测试称为集成测试,因为它们超越了无需 VS Code 实例即可运行的单元测试。本文档侧重于 VS Code 集成测试。
概述
如果你正在使用 Yeoman 生成器来搭建扩展脚手架,那么集成测试已经为你创建好了。
在生成的扩展中,你可以使用 npm run test 或 yarn test 来运行集成测试,该测试会:
- 下载并解压最新版本的 VS Code。
- 运行由扩展测试运行器脚本指定的 Mocha 测试。
快速设置:测试 CLI
VS Code 团队发布了一个用于运行扩展测试的命令行工具。你可以在 扩展示例仓库中找到示例。
该测试 CLI 提供了快速设置,并允许你使用 Extension Test Runner 轻松运行和调试 VS Code UI 测试。该 CLI 在底层完全使用 Mocha。
若要开始,首先需要安装 @vscode/test-cli 模块,以及能够支持在 VS Code 桌面版中运行测试的 @vscode/test-electron 模块。
npm install --save-dev @vscode/test-cli @vscode/test-electron
安装这些模块后,你将拥有 vscode-test 命令行工具,可以将其添加到 package.json 的 scripts 部分中。
{
"name": "my-cool-extension",
"scripts": {
+ "test": "vscode-test"
vscode-test 会在相对于当前工作目录的位置查找 .vscode-test.js/mjs/cjs 文件。此文件提供测试运行器的配置,你可以在此处找到完整的定义。
常见选项包括:
- (必需)
files- 一个模式、模式列表或包含要运行测试的绝对路径。 version- 用于运行测试的 VS Code 版本(默认为stable)。workspaceFolder- 测试期间要打开的工作区路径。extensionDevelopmentPath- 扩展文件夹的路径(默认为配置文件的目录)。mocha- 包含要传递给 Mocha 的其他选项的对象。
配置可能非常简单:
// .vscode-test.js
const { defineConfig } = require('@vscode/test-cli');
module.exports = defineConfig({ files: 'out/test/**/*.test.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)。
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));
});
});
你可以使用 npm test 命令运行此测试,或者在安装 Extension Test Runner 后,使用 VS Code 中的 Test: Run All Tests 命令。你也可以使用 Test: Debug All Tests 命令来调试测试。
高级设置:自定义运行器
你可以在 helloworld-test-sample 中找到本指南的配置。本文档的其余部分将在该示例的背景下解释这些文件。
- 测试脚本 (
src/test/runTest.ts) - 测试运行器脚本 (
src/test/suite/index.ts)
VS Code 提供了两个用于运行扩展测试的 CLI 参数:--extensionDevelopmentPath 和 --extensionTestsPath。
例如
# - 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/test-electron API 来简化下载、解压以及使用扩展测试参数启动 VS Code 的过程。
import * as path from 'path';
import { runTests } from '@vscode/test-electron';
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(err);
console.error('Failed to run tests');
process.exit(1);
}
}
main();
@vscode/test-electron API 还允许:
- 使用特定的工作区启动 VS Code。
- 下载不同版本的 VS Code,而不是最新的稳定版。
- 使用额外的 CLI 参数启动 VS Code。
你可以在 microsoft/vscode-test 找到更多 API 使用示例。
测试运行器脚本
运行扩展集成测试时,--extensionTestsPath 指向测试运行器脚本(src/test/suite/index.ts),该脚本以编程方式运行测试套件。下面是 helloworld-test-sample 的测试运行器脚本,它使用 Mocha 运行测试套件。你可以以此为起点,利用 Mocha 的 API 自定义你的设置。你也可以将 Mocha 替换为任何其他可以以编程方式运行的测试框架。
import * as path from 'path';
import * as Mocha from 'mocha';
import { glob } from 'glob';
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true
});
const testsRoot = path.resolve(__dirname, '..');
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot })
.then(files => {
// 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);
}
})
.catch(err => {
return e(err);
});
});
}
测试运行器脚本和 *.test.js 文件都可以访问 VS Code API。
这是一个示例测试(src/test/suite/extension.test.ts)。
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.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});
调试测试
调试测试与调试扩展类似。
这是一个示例 launch.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"]
}
]
}
提示
使用 Insiders 版本进行扩展开发
由于 VS Code 的限制,如果你正在使用 VS Code 稳定版并尝试在 CLI 上运行集成测试,它会报错。
Running extension tests from the command line is currently only supported if no other instance of Code is running.
通常,如果你从 CLI 运行扩展测试,测试运行的版本不能处于已运行状态。作为一种变通方法,你可以在 VS Code 稳定版中运行测试,并使用 VS Code Insiders 进行开发。只要你不是在 VS Code Insiders 中从 CLI 运行测试,而是在 VS Code 稳定版中运行,这种设置就可以正常工作。
另一种方法是从 VS Code 内部的调试启动配置运行扩展测试。这样做还有一个额外的好处,就是你甚至可以调试测试本身。
调试时禁用其他扩展
当你在 VS Code 中调试扩展测试时,VS Code 会使用全局安装的 VS Code 实例,并加载所有已安装的扩展。你可以在 launch.json 中添加 --disable-extensions 配置,或者在 @vscode/test-electron 的 runTests API 的 launchArgs 选项中添加该配置。
{
"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"]
}
]
}
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 来处理这种情况。
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();
测试工作区受信任(Workspace Trust)行为
如果你的扩展在 package.json 中声明了 capabilities.untrustedWorkspaces,请分别为受信任和不受信任的工作区添加集成测试。你无法通过扩展测试以编程方式授予或撤销工作区信任。请对受信任和不受信任的状态使用单独的测试运行。
使用 @vscode/test-cli 时,定义单独的测试配置,以便你可以独立运行每种信任状态:
- trustedWorkspaceTests:提供基准运行,其中不应用信任限制。这有助于验证扩展的全功能行为,并捕获受信任路径中的回归问题。
- untrustedWorkspaceTests:在保持工作区信任启用的情况下验证限制模式(Restricted Mode)行为。使用专用的
--user-data-dir可防止之前保存的信任决策导致此运行意外变为受信任状态。
因为每个配置都有自己的 label,你可以独立运行它们(例如 vscode-test --label trustedWorkspaceTests 和 vscode-test --label untrustedWorkspaceTests),也可以按顺序运行这两个配置。
// .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 来断言具有信任意识的行为。
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);
}
});
});
有关如何在扩展清单中声明信任要求以及如何使用 vscode.workspace.isTrusted API 的更多信息,请参阅 工作区信任扩展指南。