Skip to content

Components Test Management

Karthik Nadig edited this page Jan 27, 2021 · 1 revision

The extension provides testing for Python code through one of the supported frameworks. This involves a variety of capabilities in addition to just running tests. The following frameworks are supported:

Functionality

operations

  • install selected test framework if not installed
  • test discovery
  • run all/selected tests & suites
  • debug all/selected tests & suites
  • breakpoints while running tests
  • show (high level) test results

Note that the active Python interpreter is used for all testing operations.

resources

  • (somewhat) granular test framework config settings
  • dedicated output log

commands

  • "Python: Discover Unit Tests"
  • "Python: Run All Unit Tests"
  • "Python: Run Unit Test Method..."
  • "Python: View Unit Test Output"

UI

  • status bar element
    • shows test results
    • triggers quick pick for testing-related operations
  • pop-up (& quick pick) to configure test framework
  • code lens on tests and suites:
    • run/debug
    • results
  • output panel: "Python Test Log"
  • TBD: Test Explorer activity (top-level VSC element)

config settings

  • "python.unitTest.cwd"
  • "python.unitTest.debugPort"
  • "python.unitTest.nosetestArgs"
  • "python.unitTest.nosetestsEnabled"
  • "python.unitTest.nosetestPath"
  • "python.unitTest.promptToConfigure"
  • "python.unitTest.pyTestArgs"
  • "python.unitTest.pyTestEnabled"
  • "python.unitTest.pyTestPath"
  • "python.unitTest.unittestArgs"
  • "python.unitTest.unittestEnabled"
  • "python.unitTest.autoTestDiscoverOnSaveEnabled"

Extension Code

codebase structure

All test-related code is contained entirely under a single directory, src/client/unittests/:

src/client/unittest
├── codeLenses
├── common
│   ├── managers
│   ├── services
│   └── testVisitors
├── display
├── nosetest
│   └── services
├── pytest
│   └── services
└── unittest
    └── services

files: external API

src/client/unittest
├── serviceRegistry.ts
└── types.ts

files: top-level code

src/client/unittest
├── common
│   ├── argumentsHelper.ts
│   ├── constants.ts
│   ├── debugLauncher.ts
│   ├── managers
│   │   ├── baseTestManager.ts
│   │   └── testConfigurationManager.ts
│   ├── runner.ts
│   ├── services
│   │   ├── configSettingService.ts
│   │   ├── storageService.ts
│   │   ├── testManagerService.ts
│   │   ├── testResultsService.ts
│   │   ├── unitTestDiagnosticService.ts
│   │   └── workspaceTestManagerService.ts
│   ├── testUtils.ts
│   ├── testVisitors
│   │   ├── flatteningVisitor.ts
│   │   ├── folderGenerationVisitor.ts
│   │   └── resultResetVisitor.ts
│   ├── types.ts
│   └── xUnitParser.ts
└── main.ts

files: configuration:

src/client/unittest
├── configurationFactory.ts
├── configuration.ts
├── nosetest
│   └── testConfigurationManager.ts
├── pytest
│   └── testConfigurationManager.ts
└── unittest
    └── testConfigurationManager.ts

files: testing frameworks:

src/client/unittest
├── nosetest
│   ├── main.ts
│   ├── runner.ts
│   └── services
│       ├── argsService.ts
│       ├── discoveryService.ts
│       └── parserService.ts
├── pytest
│   ├── main.ts
│   ├── runner.ts
│   └── services
│       ├── argsService.ts
│       ├── discoveryService.ts
│       ├── parserService.ts
│       └── testMessageService.ts
└── unittest
    ├── helper.ts
    ├── main.ts
    ├── runner.ts
    ├── services
    │   ├── argsService.ts
    │   ├── discoveryService.ts
    │   └── parserService.ts
    └── socketServer.ts

files: UI features

src/client/unittest
├── codeLenses
│   ├── main.ts
│   └── testFiles.ts
└── display
    ├── main.ts
    └── picker.ts

components

  • config
  • test discovery
  • code lens
  • picker
  • runner

Adapter Script

  • source: pythonFiles/testing_tools/adapter/__main__.py
  • invocation:
    • [PYTHON] [SCRIPT] discover pytest [ARGS] -- [PYTEST ARGS]
    • SCRIPT (any python): pythonFiles/testing_tools/run_adapter.py
    • SCRIPT (python3-only): -m pythonFiles.testing_tools.adapter
  • args:
    • --simple - simplified output for use while debugging
    • --no-hide-stdio - do not hide pytest stdout/stderr during discover (useful when debugging)
    • --pretty - format output for readability
  • implicit pytest args:
    • --collect-only

Output

OUTPUT: ROOTED_TESTS[]
ROOTED_TESTS: {
    # Uniquely identifies test root.
    "rootid": str
    # Absolute path to test root.
    "root": str
    # The discovered files, suites, etc. under this root.
    "parents": PARENT[]
    # The discovered tests under this root.
    "tests": TEST[]
}
PARENT: {
    # Uniquely identifies the parent.
    "id": str
    # The kind of parent.
    "kind": "folder"|"file"|"suite"|"function"|"subtest"
    # A human-friendly identifier.
    "name": str
    # The parent's parent (i.e. in a tree from the root.
    "parentid": str
}
TEST: {
    # Uniquely identifies the test.
    "id": str
    # A human-friendly identifier.
    "name": str
    # The location of the test's source code.
    "source": str ("<FILENAME>:<LINENO>")
    # Any supported markers associated with the test.
    "markers": ("skip"|"skip-if"|"expected-failure")[]
    # The ID of the test's parent.
    "parentid": str
}

Notes:

  • IDs may be used to identify a test or parent to the test framework (e.g. to run specific tests)
  • every child of the root will have the "rootid" as its "parentid"
  • supported test markers are framework-agnostic but correspond to framework-specific ones
  • a test is considered a "subtest" if its parent is a function or a subtest
  • a subtest is a test that is effectively a parameterization of a more general test
    • in pytest parameterized tests are the only kind of subtest
  • a subtest may be a parent if there is a nested subtest in it
    • this is not supported in pytest, but is in unittest

Example:

[{
    "rootid": ".",
    "root": "/x/y/z",
    "parents": [{
        "id": "./test_spam.py",
        "kind": "file",
        "name": "test_spam.py",
        "parentid": "."
    }, {
        "id": "./test_spam.py::SpamTests",
        "kind": "suite",
        "name": "SpamTests",
        "parentid": "./test_spam.py"
    },
    "tests" [{
        "id": "./test_spam.py::test_all",
        "name": "test_all",
        "source": "test_spam.py:11",
        "markers": ["skip", "expected-failure"],
        "parentid": "./test_spam.py"
    }, {
        "id": "./test_spam.py::SpamTests::test_spam1",
        "name": "test_spam1",
        "source": "test_spam.py:23",
        "markers": ["skip"],
        "parentid": "./test_spam.py::SpamTests"
    }]
}]
Clone this wiki locally