• vscode插件开发之 - TestController


     TesController概要介绍

     TestController 组件是用于实现自定义测试框架和集成测试结果的。它允许开发者定义自己的测试运行器,以支持在VSCode中运行和展示测试。以下是一些使用 TestController 组件的主要场景:

    自定义测试框架:如果你正在开发或使用一个非标准的测试框架,你可以使用 TestController 来集成这个框架的测试结果。

    语言特定的测试:对于某些语言或框架,VSCode可能没有内置的测试支持。使用 TestController,你可以为这些语言或框架添加测试支持。

    集成外部测试工具:如果你需要在VSCode中展示由外部测试工具生成的测试结果,TestController 可以用来映射这些结果到VSCode的测试UI。

    测试结果可视化:通过 TestController,你可以控制测试结果如何在VSCode的测试面板中展示,包括测试的通过、失败、跳过等状态。

    如何开发一款自定义的测试框架插件

      要开发一款基于vscode的自定义测试框架非常简单,有三个步骤。以读取markdown中的代码,执行测试为例子来看看如何自定义测试框架插件

    步骤一:解析文档中内容,并将结果添加到testcontroller的testItem对象

      下面的代码中,对给定的文档内容通过正则表达式进行match,获取到markdown文件中的测试name,experssion,expected内容,并将parse出来的内容用于构建TestItem。下图图一假设是文本上的代码内容,下图图二是parse出来的Test对象内容。

    1. function loadTestsFromDocument(testController: vscode.TestController, document: vscode.TextDocument) {
    2. const tests = parseTests(document.getText());
    3. for (const test of tests) {
    4. const testItem = testController.createTestItem(test.name, test.expression + "=" + test.expected);
    5. testController.items.add(testItem);
    6. }
    7. }
    8. interface Test {
    9. name: string;
    10. expression: string;
    11. expected: number;
    12. }
    13. function parseTests(text: string): Test[] {
    14. const testRegex = /^(\d+ \+ \d+) = (\d+) \/\/ (.+)$/gm;
    15. const tests: Test[] = [];
    16. let match;
    17. while ((match = testRegex.exec(text)) !== null) {
    18. tests.push({
    19. name: match[3],
    20. expression: match[1],
    21. expected: parseInt(match[2])
    22. });
    23. }
    24. return tests;
    25. }

      上述代码中,TestController.createTestItem 方法用于创建一个新的 TestItem,它代表一个测试用例。以下是 createTestItem 方法的一些关键参数和它们的说明:输入参数:
    id (string): 测试用例的唯一标识符。这里是用Test.name作为id
    label (string): 测试用例的显示名称,通常在UI中展示给用户。这里是组装成的label信息,后面要通过解析label信息来执行测试。testItem.label=test.expression + "=" + test.expected
    uri (vscode.Uri): 表示测试用例所属文件的位置。通常使用当前编辑器的文档URI。
    range (vscode.Range): 测试用例在文件中的位置范围。这有助于用户快速定位到测试用例的代码。
    children (TestItem[], optional): 如果这个测试用例是一个容器,比如一个测试套件,你可以在这里提供子测试用例的数组。
    tags (string[], optional): 一组标签,可以用来对测试用例进行分类或标记。

    步骤二:自定义测试执行逻辑

      下面定义了一个简单的runTest逻辑,通过解析testItem.label信息,判断expected和actual的值是否相等来判断测试执行结果。

    1. async function runTest(testItem: vscode.TestItem) {
    2. const expression = testItem.label.split('=')[0].trim();
    3. const expected = parseInt(testItem.label.split('=')[1].trim());
    4. const actual = eval(expression);
    5. const result = actual === expected;
    6. if (result) {
    7. testItem.busy = false;
    8. return `${testItem.label}: PASSED`;
    9. } else {
    10. testItem.busy = false;
    11. return `${testItem.label}: FAILED`;
    12. }
    13. }

    步骤三:注册命令执行测试

      定义好前面的内容后,就可以注册命令,将testController.items的内容转换成数组,在逐个执行runTest方法,并把执行结果通过showInfomationMessage显示出来。

    1. vscode.commands.registerCommand('markdownTestController.runTests', async () => {
    2. const tests = Array.from(testController.items)
    3. const results: string[] = [];
    4. for (const [, testItem] of tests) {
    5. vscode.window.showInformationMessage(JSON.stringify(testItem));
    6. const resultMessage = await runTest(testItem);
    7. results.push(resultMessage);
    8. }
    9. if (results.length > 0) {
    10. vscode.window.showInformationMessage(results.join('\n'));
    11. } else {
    12. vscode.window.showInformationMessage('No tests executed.');
    13. }
    14. });
    15. }

      编写好脚本后,就可以执行了,在markdown文件中准备了一个数学计算,然后执行命令,可以看到message中显示执行结果,另外,为了调试,这里还显示testItem对象的值。

      除了自定义测试执行逻辑,实际在开发vscode测试相关类插件时,还可以调用第三方已有的测试工具执行测试,代码的例子是调用jest执行测试的例子。

      上面只定义了一个简单的runTest逻辑,在实际项目中,更多的是集成第三方测试执行插件,例如集成jest,在runTest方法里面只需通过“child_process.exec(`npx jest -t "${testItem.label}" --json`”来执行对应的测试即可。下面的使用vscode的testcontroller等组件,集成test来执行测试的例子。所有代码如下所示:

    1. import * as vscode from 'vscode';
    2. import * as child_process from 'child_process';
    3. export function activate(context: vscode.ExtensionContext) {
    4. const testController = vscode.tests.createTestController('jestTestController', 'Jest Tests');
    5. context.subscriptions.push(testController);
    6. context.subscriptions.push(vscode.commands.registerCommand('extension.runJestTests', async () => {
    7. await runAllTests(testController);
    8. }));
    9. async function runAllTests(testController: vscode.TestController) {
    10. const testItems: vscode.TestItem[] = [];
    11. testController.items.forEach(testItem => testItems.push(testItem));
    12. const request = new vscode.TestRunRequest(testItems);
    13. const run = testController.createTestRun(request);
    14. let allTestsPassed = true;
    15. const testResults: { [key: string]: boolean } = {};
    16. for (const test of testItems) {
    17. run.started(test);
    18. const result = await runJestTest(test);
    19. testResults[test.id] = result;
    20. if (result) {
    21. run.passed(test);
    22. } else {
    23. run.failed(test, new vscode.TestMessage('Test failed'));
    24. allTestsPassed = false;
    25. }
    26. }
    27. run.end();
    28. if (allTestsPassed) {
    29. vscode.window.showInformationMessage('All tests passed.');
    30. } else {
    31. vscode.window.showInformationMessage('Some tests failed. Check test results for details.');
    32. }
    33. }
    34. context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(doc => {
    35. if (doc.languageId === 'typescript' || doc.languageId === 'javascript') {
    36. loadTestsFromDocument(testController, doc);
    37. }
    38. }));
    39. vscode.workspace.textDocuments.forEach(doc => {
    40. if (doc.languageId === 'typescript' || doc.languageId === 'javascript') {
    41. loadTestsFromDocument(testController, doc);
    42. }
    43. });
    44. }
    45. function loadTestsFromDocument(testController: vscode.TestController, document: vscode.TextDocument) {
    46. const tests = parseTests(document.getText());
    47. for (const test of tests) {
    48. const testItem = testController.createTestItem(test.name, test.name, document.uri);
    49. testController.items.add(testItem);
    50. }
    51. }
    52. interface Test {
    53. name: string;
    54. }
    55. function parseTests(text: string): Test[] {
    56. const testRegex = /test\(['"](.+)['"]/g;
    57. const tests: Test[] = [];
    58. let match;
    59. while ((match = testRegex.exec(text)) !== null) {
    60. tests.push({
    61. name: match[1]
    62. });
    63. }
    64. return tests;
    65. }
    66. async function runJestTest(testItem: vscode.TestItem): Promise<boolean> {
    67. return new Promise((resolve) => {
    68. const options = {
    69. cwd: vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : undefined,
    70. };
    71. child_process.exec(`npx jest -t "${testItem.label}" --json`, options, (err, stdout, stderr) => {
    72. if (err) {
    73. console.error(stderr);
    74. vscode.window.showErrorMessage(`Test failed to run: ${stderr}`);
    75. resolve(false);
    76. } else {
    77. try {
    78. const result = JSON.parse(stdout);
    79. vscode.window.showInformationMessage(`${testItem.label}: ${result.numFailedTests === 0 ? 'Passed' : 'Failed'}`);
    80. resolve(result.numFailedTests === 0);
    81. } catch (parseError) {
    82. console.error(`Failed to parse test result: ${parseError}`);
    83. vscode.window.showErrorMessage(`Failed to parse test result: ${parseError}`);
    84. resolve(false);
    85. }
    86. }
    87. });
    88. });
    89. }
    90. export function deactivate() { }

       为了验证上面的插件是否工作,需要再准备一个包含jest测试的项目,该项目包含一个简单sum函数,以及用jest框架测试add函数的测试脚本。

    1. import { add } from './adder';
    2. test('first', () => {
    3. expect(add(1, 2)).toBe(4);
    4. });
    5. test('second', () => {
    6. expect(add(0, 0)).toBe(0);
    7. });

        运行插件,可以看到执行了两个测试,其中一个成功,一个失败,说明成功调用jest执行了测试,并获取到了测试结果。结果如下图所示:

       以上就是vscode插件开发TestController组件的使用介绍,在实际项目,例如playwright-vscode插件就会使用这些组件,完成对ui测试的执行。后续的博客将从源码层面来解析playwright-vscode插件实现原理。

  • 相关阅读:
    软件测试Day7|数据库Mysql
    Go语言中定时任务库Cron使用详解
    上周热点回顾(7.17-7.23)
    R语言使用econocharts包创建微观经济或宏观经济图、indifference函数可视化无差异曲线(indifference curve)
    Using power MOSFETs in parallel
    【算法训练营】 - ⑩ 并查集与图
    IPQ6010+QCN9074|QCN9074-6E Throughput Test Report in DR6018
    python工作任务流flow实时框架:prefect
    网安周报|Mixin Network 云服务商数据库遭到攻击,涉案金额约 2 亿美元
    CSS3入门
  • 原文地址:https://blog.csdn.net/qiaotl/article/details/139835164