🚀 在 VS Code 中

Visual Studio Code 中的 Python 测试

Python 扩展支持使用 Python 内置的 unittest 框架和 pytest 进行测试。

单元测试的简要背景

(如果您已经熟悉单元测试,可以跳到演练。)

单元是要测试的特定代码片段,例如函数或类。 单元测试是其他代码片段,专门使用各种不同的输入(包括边界和边缘情况)来执行代码单元。 unittest 和 pytest 框架都可用于编写单元测试。

例如,假设您有一个函数来验证用户在 Web 表单中输入的帐号格式

def validate_account_number_format(account_string):
    # Return False if invalid, True if valid
    # ...

单元测试仅关注单元的接口——它的参数和返回值——而不是它的实现(这就是为什么函数体中没有显示任何代码;通常您会使用其他经过良好测试的库来帮助实现该函数)。 在此示例中,该函数接受任何字符串,如果该字符串包含格式正确的帐号,则返回 true,否则返回 false。

为了彻底测试此函数,您需要向其抛出所有可能的输入:有效字符串、拼写错误的字符串(相差一个或两个字符,或包含无效字符)、过短或过长的字符串、空白字符串、空参数、包含控制字符(非文本代码)的字符串、包含 HTML 的字符串、包含注入攻击(例如 SQL 命令或 JavaScript 代码)的字符串等等。 如果验证后的字符串稍后在数据库查询中使用或显示在应用程序的 UI 中,则测试注入攻击等安全案例尤其重要。

对于每个输入,然后定义函数的预期返回值(或多个值)。 同样,在本例中,该函数应仅对格式正确的字符串返回 true。(号码本身是否为真实帐户是另一回事,将在其他地方通过数据库查询处理。)

掌握所有参数和预期返回值后,现在编写测试本身,这些测试是使用特定输入调用函数,然后将实际返回值与预期返回值进行比较的代码片段(此比较称为断言

# Import the code to be tested
import validator

# Import the test framework (this is a hypothetical module)
import test_framework

# This is a generalized example, not specific to a test framework
class Test_TestAccountValidator(test_framework.TestBaseClass):
    def test_validator_valid_string():
        # The exact assertion call depends on the framework as well
        assert(validate_account_number_format("1234567890"), True)

    # ...

    def test_validator_blank_string():
        # The exact assertion call depends on the framework as well
        assert(validate_account_number_format(""), False)

    # ...

    def test_validator_sql_injection():
        # The exact assertion call depends on the framework as well
        assert(validate_account_number_format("drop database master"), False)

    # ... tests for all other cases

代码的确切结构取决于您使用的测试框架,本文后面提供了具体示例。 在任何情况下,正如您所看到的,每个测试都很简单:使用参数调用函数并断言预期的返回值。

所有测试的组合结果是您的测试报告,它告诉您该函数(单元)在所有测试用例中是否按预期运行。 也就是说,当一个单元通过其所有测试时,您可以确信它运行正常。(测试驱动开发的实践是您实际上首先编写测试,然后编写代码以通过越来越多的测试,直到所有测试都通过。)

由于单元测试是小的、隔离的代码片段(在单元测试中,您避免外部依赖项并使用模拟数据或以其他方式模拟输入),因此它们的运行速度很快且成本不高。 此特性意味着您可以尽早且经常地运行单元测试。 开发人员通常在将代码提交到存储库之前甚至运行单元测试;门控签入系统也可以在合并提交之前运行单元测试。 许多持续集成系统也在每次构建后运行单元测试。 尽早且经常地运行单元测试意味着您可以快速捕获回归,即先前通过所有单元测试的代码的行为中意外的更改。 由于测试失败可以很容易地追溯到特定的代码更改,因此很容易找到并补救失败的原因,这无疑比在流程后期才发现问题要好得多!

有关单元测试的一般背景,请阅读 Wikipedia 上的 单元测试。 有关有用的单元测试示例,您可以查看 https://github.com/gwtw/py-sorting,这是一个包含不同排序算法测试的存储库。

示例测试演练

Python 测试是 Python 类,它们驻留在与被测代码不同的文件中。 每个测试框架都指定测试和测试文件的结构和命名。 编写测试并启用测试框架后,VS Code 会找到这些测试,并为您提供各种命令来运行和调试它们。

对于本节,创建一个文件夹并在 VS Code 中打开它。 然后创建一个名为 inc_dec.py 的文件,其中包含以下要测试的代码

def increment(x):
    return x + 1

def decrement(x):
    return x - 1

使用此代码,您可以体验在 VS Code 中使用测试,如下节所述。

配置测试

安装 Python 扩展并在编辑器中打开 Python 文件后,测试烧杯图标将显示在 VS Code 活动栏上。 烧杯图标用于 测试资源管理器视图。 打开测试资源管理器时,如果您没有启用测试框架,您将看到一个配置测试按钮。 选择配置测试后,系统将提示您选择测试框架和包含测试的文件夹。 如果您使用 unittest,系统还会要求您选择用于标识测试文件的文件 glob 模式。

注意:文件 glob 模式是定义的字符串模式,它根据通配符匹配文件或文件夹名称,然后包含或不包含。

Configure Python Tests button displayed in the Test Explorer when tests haven't been configured.

您可以使用命令面板中的 Python: 配置测试命令随时配置测试。 您还可以通过手动设置 python.testing.unittestEnabledpython.testing.pytestEnabled 来手动配置测试,这可以在“设置”编辑器中或在 settings.json 文件中完成,如 VS Code 设置文档中所述。 每个框架还具有特定的配置设置,如其文件夹和模式的测试配置设置中所述。

如果同时启用了两个框架,则 Python 扩展将仅运行 pytest

如果您启用 pytest,如果 pytest 框架包尚未在当前激活的环境中,VS Code 会提示您安装它

VS Code prompt to install a test framework when enabled

注意:python 扩展支持的 pytest 最低版本为 7.0.0。

创建测试

每个测试框架都有自己命名测试文件和构建测试的约定,如下节所述。 每个案例都包含两个测试方法,其中一个测试方法被有意设置为失败,用于演示目的。

unittest 中的测试

创建一个名为 test_unittest.py 的文件,其中包含一个带有两个测试方法的测试类

import inc_dec    # The code to test
import unittest   # The test framework

class Test_TestIncrementDecrement(unittest.TestCase):
    def test_increment(self):
        self.assertEqual(inc_dec.increment(3), 4)

    # This test is designed to fail for demonstration purposes.
    def test_decrement(self):
        self.assertEqual(inc_dec.decrement(3), 4)

if __name__ == '__main__':
    unittest.main()

pytest 中的测试

创建一个名为 test_pytest.py 的文件,其中包含两个测试方法

import inc_dec    # The code to test

def test_increment():
    assert inc_dec.increment(3) == 4

# This test is designed to fail for demonstration purposes.
def test_decrement():
    assert inc_dec.decrement(3) == 4

测试发现

默认情况下,Python 扩展会在您启用框架后尝试发现测试。 您也可以随时使用命令面板中的 测试: 刷新测试命令触发测试发现。

python.testing.autoTestDiscoverOnSaveEnabled 默认设置为 true,这意味着每当您在工作区中添加、删除或更新任何 Python 文件时,也会自动执行测试发现。 要禁用此功能,请将值设置为 false。 您可以通过在 python.testing.autoTestDiscoverOnSavePattern 设置中指定 glob 模式来优化自动测试发现发生的文件。 其默认值为 **/*.py

您可以在“设置”编辑器中或直接在 settings.json 文件中配置设置。 您需要重新加载窗口才能使测试发现设置生效。

测试发现将应用当前框架的发现模式(可以使用测试配置设置进行自定义)。 默认行为如下:

  • python.testing.unittestArgs:在顶层项目文件夹中查找名称中包含“test”的任何 Python (.py) 文件。 所有测试文件都必须是可导入的模块或包。 您可以使用 -p 配置设置自定义文件匹配模式,并使用 -t 设置自定义文件夹。

  • python.testing.pytestArgs:查找名称以“test_”开头或以“_test”结尾的任何 Python (.py) 文件,这些文件位于当前文件夹和所有子文件夹中的任何位置。

提示:有时,放置在子文件夹中的测试不会被发现,因为此类测试文件无法导入。 要使它们可导入,请在该文件夹中创建一个名为 __init__.py 的空文件。

如果测试发现成功,您将在测试资源管理器中看到列出的测试

The VS Code Test Explorer for Python tests

如果发现失败(例如,未安装测试框架或测试文件中存在语法错误),您将在测试资源管理器中看到错误消息。 您可以查看 Python 输出面板以查看完整错误消息(使用 查看 > 输出 菜单命令显示 输出 面板,然后从右侧的下拉列表中选择 Python)。

Discovery failure error messaged displayed in the Test Explorer

一旦 VS Code 识别出测试,它将提供几种运行这些测试的方法,如运行测试中所述。

运行测试

您可以使用以下任一操作运行测试:

  • 在打开测试文件的情况下,选择测试定义行旁边的装订线中显示的绿色运行图标,如上一节所示。 此命令仅运行该一个方法。

    Run test icon displayed on the gutter when the test file is open in the editor

  • 命令面板,通过运行以下任一命令:

    • 测试: 运行所有测试 - 运行所有已发现的测试。
    • 测试: 运行当前文件中的测试 - 运行编辑器中打开的文件中的所有测试。
    • 测试: 运行光标处的测试 - 仅运行编辑器中光标下的测试方法。
  • 测试资源管理器

    • 要运行所有已发现的测试,请选择 测试资源管理器顶部的播放按钮

      Running all tests through Test Explorer

    • 要运行特定的测试组或单个测试,请选择文件、类或测试,然后选择该项目右侧的播放按钮

      Running tests at specific scopes through Test Explorer

    • 您还可以通过测试资源管理器运行选定的测试。 为此,Ctrl+单击(或 macOS 上的 Cmd+单击)要运行的测试,右键单击其中一个测试,然后选择 运行测试

测试运行后,VS Code 会直接在编辑器中以装订线装饰的形式显示结果。 失败的测试也会在编辑器中突出显示,并带有显示测试运行错误消息和所有测试运行历史记录的速览视图。 您可以按 Escape 键关闭视图,您可以通过打开用户设置(首选项: 打开设置 (UI) 命令在 命令面板中)并将 测试: 自动打开速览视图 设置的值更改为 never 来禁用它。

测试资源管理器中,结果显示为单个测试以及包含这些测试的任何类和文件。 如果该文件夹中的任何测试未通过,文件夹将显示失败图标。

Test results on a unittest class and in Test Explorer

VS Code 还在 Python 测试日志输出面板中显示测试结果。

Test results in the Python Test Log output panel

并行运行测试

通过 pytest-xdist 包支持使用 pytest 并行运行测试。 要启用并行测试:

  1. 打开集成终端并安装 pytest-xdist 包。 有关更多详细信息,请参阅项目的文档页面

    对于 Windows:

    py -3 -m pip install pytest-xdist
    

    对于 macOS/Linux:

    python3 -m pip install pytest-xdist
    
  2. 接下来,在您的项目目录中创建一个名为 pytest.ini 的文件,并添加以下内容,指定要使用的 CPU 数量。 例如,将其设置为 4 个 CPU:

     [pytest]
     addopts=-n4
    

    或者,如果您使用的是 pyproject.toml 文件:

     [tool.pytest.ini_options]
     addopts="-n 4"
    
  3. 运行您的测试,这些测试现在将并行运行。

运行带覆盖率的测试

测试覆盖率衡量您的代码中有多少被测试覆盖,这可以帮助您识别代码中未被充分测试的区域。 有关测试覆盖率的更多信息,请访问 VS Code 的测试覆盖率文档

提示:当前仅当将 "python.experiments.optInto": ["pythonTestAdapter"] 添加到您的用户 settings.json 时,才支持运行带覆盖率的 Python 测试。

要运行启用覆盖率的测试,请选择测试资源管理器中的覆盖率运行图标,或从您通常从中触发测试运行的任何菜单中选择“运行带覆盖率”选项。 如果您使用 pytest,Python 扩展将使用 pytest-cov 插件运行覆盖率,或者对于 unittest,将使用 coverage.py 插件运行覆盖率。

注意:在运行带覆盖率的测试之前,请确保为您的项目安装正确的测试覆盖率包。

覆盖率运行完成后,将在编辑器中突出显示行级覆盖率的行。 测试覆盖率结果将显示为测试资源管理器中的“测试覆盖率”子选项卡,您也可以使用命令面板中的 测试: 聚焦于测试覆盖率视图 (F1) 导航到此面板。 在此面板上,您可以查看工作区中每个文件和文件夹的行覆盖率指标。

Gif showing running Python tests with coverage.

为了更精细地控制使用 pytest 时的覆盖率运行,您可以编辑 python.testing.pytestArgs 设置以包含您的规范。 当 pytest 参数 --cov 存在于 python.testing.pytestArgs 中时,Python 扩展将不对覆盖率参数进行任何额外的编辑,以允许您的自定义生效。 如果未找到 --cov 参数,扩展将在运行之前将 --cov=. 添加到 pytest 参数,以在工作区根目录启用覆盖率。

调试测试

您可能偶尔需要单步执行和分析调试器中的测试,原因可能是测试本身有您需要跟踪的代码缺陷,或者为了更好地理解为什么被测代码区域失败。 有关调试的更多信息或了解它在 VS Code 中的工作方式,您可以阅读Python 调试配置和常规 VS Code 调试文章。

例如,前面给出的 test_decrement 函数失败是因为断言本身存在缺陷。 以下步骤演示如何分析测试:

  1. test_decrement 函数的第一行设置断点。

  2. 右键单击函数定义旁边的装订线装饰,然后选择 调试测试,或选择 测试资源管理器中该测试旁边的 调试测试 图标。 VS Code 启动调试器并在断点处暂停。

    Debug Test icon in the Test Explorer

  3. 调试控制台 面板中,输入 inc_dec.decrement(3) 以查看实际结果为 2,而测试中指定的预期结果是不正确的 4。

  4. 停止调试器并更正错误代码:

    # unittest
    self.assertEqual(inc_dec.decrement(3), 2)
    
    # pytest
    assert inc_dec.decrement(3) == 2
    
  5. 保存文件并再次运行测试以确认它们通过,并查看装订线装饰也指示通过状态。

    注意:运行或调试测试不会自动保存测试文件。 始终确保在运行测试之前保存对测试的更改,否则您可能会对结果感到困惑,因为它们仍然反映文件的先前版本!

您可以使用命令面板中的以下命令来调试测试:

  • 测试: 调试所有测试 - 为工作区中的所有测试启动调试器。
  • 测试: 调试当前文件中的测试 - 为您在编辑器中打开的文件中定义的测试启动调试器。
  • 测试: 调试光标处的测试 - 仅为您将光标焦点放在编辑器中的方法启动调试器。 您还可以使用测试资源管理器中的 调试测试 图标为选定范围内的所有测试和所有已发现的测试启动调试器。

您还可以通过将 testing.defaultGutterClickAction 设置值更改为 debug 在您的 settings.json 文件中,更改单击装订线装饰的默认行为,使其调试测试而不是运行测试。

调试器的工作方式与测试其他 Python 代码的方式相同,包括断点、变量检查等等。 要自定义调试测试的设置,您可以在工作区的 .vscode 文件夹中的 launch.json 文件中指定 "purpose": ["debug-test"]。 当您运行 测试: 调试所有测试测试: 调试当前文件中的测试测试: 调试光标处的测试 命令时,将使用此配置。

例如,launch.json 文件中的以下配置禁用了调试测试的 justMyCode 设置:

{
  "name": "Python: Debug Tests",
  "type": "debugpy",
  "request": "launch",
  "program": "${file}",
  "purpose": ["debug-test"],
  "console": "integratedTerminal",
  "justMyCode": false
}

如果您有多个带有 "purpose": ["debug-test"] 的配置条目,则将使用第一个定义,因为我们目前不支持此请求类型的多个定义。

测试命令

以下是 VS Code 中 Python 扩展支持的所有测试命令。 这些都可以在命令面板中找到:

命令名称 描述
Python: 配置测试 配置要与 Python 扩展一起使用的测试框架。
测试: 清除所有结果 清除所有测试状态,因为 UI 会跨会话持久保存测试结果。
测试: 调试失败的测试 调试最近一次测试运行中失败的测试。
测试: 调试上次运行 调试最近一次测试运行中执行的测试。
测试: 调试光标处的测试 调试光标焦点位于编辑器中的测试方法。 类似于 2021.9 之前版本上的 Python: 调试测试方法...
测试: 调试当前文件中的测试 调试编辑器中当前处于焦点状态的文件中的测试。
测试: 转到下一个测试失败 如果错误速览视图已打开,则打开并移动到资源管理器中下一个失败的测试的速览视图。
测试: 转到上一个测试失败 如果错误速览视图已打开,则打开并移动到资源管理器中上一个失败的测试的速览视图。
测试: 速览输出 为失败的测试方法打开错误速览视图。
测试: 刷新测试 执行测试发现并更新测试资源管理器以反映任何测试更改、添加或删除。 类似于 2021.9 之前版本上的 Python: 发现测试
测试: 重新运行失败的测试 重新运行最近一次测试运行中失败的测试。 类似于 2021.9 之前版本上的 Python: 运行失败的测试
测试: 重新运行上次运行 调试最近一次测试运行中执行的测试。
测试: 运行所有测试 运行所有已发现的测试。 等效于 2021.9 之前版本上的 Python: 运行所有测试
测试: 运行光标处的测试 运行光标焦点位于编辑器中的测试方法。 类似于 2021.9 之前版本上的 Python: 运行测试方法...
测试: 运行当前文件中的测试 运行编辑器中当前处于焦点状态的文件中的测试。 等效于 2021.9 之前版本上的 Python: 运行当前测试文件
测试: 显示输出 打开包含所有测试运行详细信息的输出。 类似于 2021.9 之前版本上的 Python: 显示测试输出
测试: 聚焦于测试资源管理器视图 打开测试资源管理器视图。 类似于 2021.9 之前版本上的 测试: 聚焦于 Python 视图
测试: 停止刷新测试 取消测试发现。

Django 单元测试

Python 扩展还提供对发现和运行 Django 单元测试的支持! 您只需执行一些额外的设置步骤即可发现您的 Django 测试:

提示:当前仅当将 "python.experiments.optInto": ["pythonTestAdapter"] 添加到您的用户 settings.json 时,才支持 Django 测试。

  1. 在您的 settings.json 文件中设置 "python.testing.unittestEnabled": true,
  2. 添加 MANAGE_PY_PATH 作为环境变量
    1. 在您的项目根目录中创建一个 .env 文件。
    2. MANAGE_PY_PATH='<path-to-manage.py>' 添加到 .env 文件,将 <path-to-manage.py> 替换为应用程序的 manage.py 文件的路径。

      提示:您可以通过右键单击资源管理器视图中的文件并选择 复制路径 来复制路径。

  3. 根据需要将 Django 测试参数添加到 settings.json 文件中的 "python.testing.unittestArgs": [],并删除任何与 Django 不兼容的参数。

注意:默认情况下,Python 扩展会在项目根目录中查找并加载 .env 文件。 如果您的 .env 文件不在项目根目录中,或者您正在使用 VS Code 变量替换,请将 "python.envFile": "${workspaceFolder}/<path-to-.env>" 添加到您的 settings.json 文件。 这使 Python 扩展能够在运行和发现测试时从此文件加载环境变量。 获取有关 Python 环境变量的更多信息。

导航到测试视图,然后选择 刷新测试 按钮以显示您的 Django 测试!

故障排除

如果您的 Django 单元测试未在测试视图中显示,请尝试以下故障排除步骤:

  • Python 输出面板中搜索错误消息。 它们可能会提示您的测试未被发现的原因。

  • 尝试在终端中运行 Django 测试。 然后将相同的命令“翻译”为 VS Code 设置。 例如,如果您在终端中运行 python manage.py test --arg,您会将 MANAGE_PY_PATH='./manage.py' 添加到 .env 文件,并在 VS Code 设置中设置 "python.testing.unittestArgs": [--arg]

    或者,您也可以在 Python 输出面板中找到 Python 扩展运行的命令。

  • 如果最初使用的是相对路径,请在使用绝对路径设置 MANAGE_PY_PATH 环境变量时。

pytest 的 IntelliSense

Pylance 提供 IntelliSense 功能,可以帮助您更有效地使用 pytest fixture参数化测试

当您键入测试函数的参数时,Pylance 会为您提供一个补全列表,其中包含来自 @pytest.mark.parametrize 装饰器的参数名称,以及在您的测试文件或 conftest.py 中定义的现有 pytest fixture。 还支持代码导航功能(例如 转到定义查找所有引用)和重命名符号重构

Auto completion suggestion when passing a parameter to a test function in a Python test file. The suggestion is a fixture defined in conftest.py.

当悬停在 fixture 引用或参数化参数引用上时,Pylance 将显示推断的类型注释,或者基于 fixture 的返回值,或者基于传递给参数化装饰器的参数的推断类型。

Pylance type inference based on the types of arguments passed to the parameterization decorator.

Pylance 还提供代码操作,以将类型注释添加到具有 fixture 参数的测试函数。 还可以通过在您的用户设置中将 python.analysis.inlayHints.pytestParameters 设置为 true 来启用推断的 fixture 参数类型的内联提示。

Code action to add type annotation when hoving over a test function with a fixture parameter

测试配置设置

Python 测试的行为由 VS Code 提供的通用 UI 设置以及特定于 Python 和您已启用的任何框架的设置驱动。

通用 UI 设置

影响测试功能 UI 的设置由 VS Code 本身提供,当您搜索“Testing”时,可以在VS Code 设置编辑器中找到。

通用 Python 设置

设置
(python.testing.)
默认值 描述
autoTestDiscoverOnSaveEnabled true 指定在保存测试文件时是否启用或禁用自动运行测试发现。 您可能需要在更改此设置后重新加载窗口才能应用它。
autoTestDiscoverOnSavePattern **/*.py 指定 glob 模式,该模式确定当 autoTestDiscoverOnSaveEnabledtrue 时哪些文件更改触发自动测试发现。
cwd null 指定测试的可选工作目录。
debugPort 3000 用于调试 unittest 测试的端口号。
promptToConfigure true 指定如果发现潜在的测试,VS Code 是否提示配置测试框架。

unittest 配置设置

Unittest 设置
(python.testing.)
默认值 描述
unittestEnabled false 指定是否启用 unittest 作为测试框架。 应禁用 pytest 的等效设置。
unittestArgs ["-v", "-s", ".", "-p", "*test*.py"] 要传递给 unittest 的参数,其中以空格分隔的每个元素都是列表中的单独项。 有关默认值的说明,请参见下文。

unittest 的默认参数如下:

  • -v 设置默认详细程度。 删除此参数可获得更简单的输出。
  • -s . 指定发现测试的起始目录。 如果您的测试在“test”文件夹中,请将参数更改为 -s test(表示参数数组中的 "-s", "test")。
  • -p *test*.py 是用于查找测试的发现模式。 在这种情况下,它是任何包含单词“test”的 .py 文件。 如果您以不同的方式命名测试文件,例如将“_test”附加到每个文件名,则在数组的适当参数中使用类似 *_test.py 的模式。

要停止第一次失败时的测试运行,请将快速失败选项 "-f" 添加到参数数组。

有关所有可用选项的完整集,请参阅 unittest 命令行界面

pytest 配置设置

pytest 设置
(python.testing.)
默认值 描述
pytestEnabled false 指定是否启用 pytest 作为测试框架。 应禁用 unittest 的等效设置。
pytestPath "pytest" pytest 路径。如果 pytest 位于当前环境之外,请使用完整路径。
pytestArgs [] 传递给 pytest 的参数,其中以空格分隔的每个元素都是列表中的单独项。请参阅 pytest 命令行选项

您还可以使用 pytest.ini 文件配置 pytest,如 pytest 配置 中所述。

注意 如果您安装了 pytest-cov 覆盖率模块,则 VS Code 在调试时不会在断点处停止,因为 pytest-cov 使用相同的技术来访问正在运行的源代码。为了防止此行为,请在调试测试时在 pytestArgs 中包含 --no-cov,例如通过将 "env": {"PYTEST_ADDOPTS": "--no-cov"} 添加到您的调试配置中。(请参阅上面关于如何设置该启动配置的 Debug Tests。)(有关更多信息,请参阅 pytest-cov 文档中的 Debuggers and PyCharm。)

IntelliSense 设置

IntelliSense 设置
(python.analysis.)
默认值 描述
inlayHints.pytestParameters false 是否显示 pytest fixture 参数类型的内联提示。接受的值为 truefalse

另请参阅

  • Python 环境 - 控制用于编辑和调试的 Python 解释器。
  • 设置参考 - 探索 VS Code 中与 Python 相关的全部设置。