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
代码的具体结构取决于您使用的测试框架,本文后面将提供具体示例。无论如何,如您所见,每个测试都很简单:使用参数调用函数,并断言预期返回值。
所有测试的组合结果是您的测试报告,它告诉您函数(单元)是否在所有测试用例中都按预期工作。也就是说,当单元通过所有测试时,您可以确信它运行正常。(**测试驱动开发** 的做法是先编写测试,然后编写代码来通过越来越多的测试,直到所有测试都通过。)
由于单元测试是小的、隔离的代码段(在单元测试中,您避免外部依赖关系,并使用模拟数据或其他模拟输入),因此它们运行起来快速且成本低廉。这种特性意味着您可以在早期和频繁地运行单元测试。开发人员通常甚至在将代码提交到存储库之前就运行单元测试;门控签入系统也可以在合并提交之前运行单元测试。许多持续集成系统也在每次构建后运行单元测试。在早期和频繁地运行单元测试意味着您可以快速发现 **回归**,即以前通过了所有单元测试的代码行为的意外变化。由于测试失败很容易追溯到特定的代码更改,因此很容易找到并修复失败的原因,这无疑比在流程的后期才发现问题要好得多!
有关单元测试的一般背景知识,请阅读维基百科上的 单元测试。要查看有用的单元测试示例,可以查看 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 模式是一种定义的字符串模式,它使用通配符匹配文件或文件夹名称,然后选择包括或不包括。
您可以随时使用 命令面板 中的 **Python: 配置测试** 命令来配置测试。您也可以通过设置 python.testing.unittestEnabled
或 python.testing.pytestEnabled
来手动配置测试,这可以在设置编辑器或 settings.json
文件中完成,如 VS Code 设置 文档中所述。每个框架也有特定的配置设置,如 测试配置设置 中对其文件夹和模式的描述。
如果同时启用了这两个框架,Python 扩展将只运行 pytest
。
如果您启用 pytest,VS Code 会提示您安装框架包(如果当前激活的环境中尚未安装)。
创建测试
每个测试框架都有自己的命名测试文件和构建测试结构的约定,如以下各节所述。每个案例都包含两个测试方法,其中一个有意设置为失败,用于演示目的。
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
,这可以在设置编辑器或 settings.json
文件中完成,如 VS Code 设置 文档中所述。您需要重新加载窗口才能使此设置生效。
测试发现会应用当前框架的发现模式(可以使用 测试配置设置 自定义)。默认行为如下
-
python.testing.unittestArgs
:在顶级项目文件夹中查找任何名称中包含“test”的 Python(.py
)文件。所有测试文件必须是可导入的模块或包。可以使用-p
配置设置自定义文件匹配模式,并使用-t
设置自定义文件夹。 -
python.testing.pytestArgs
:查找任何名称以“test_”开头或以“_test”结尾的 Python(.py
)文件,这些文件位于当前文件夹和所有子文件夹中。
提示:有时放置在子文件夹中的测试不会被发现,因为这些测试文件无法导入。为了使其可导入,请在该文件夹中创建一个名为
__init__.py
的空文件。
如果测试发现成功,您将在测试资源管理器中看到列出的测试
如果发现失败(例如,测试框架未安装或您的测试文件中存在语法错误),您将在测试资源管理器中看到错误消息。您可以检查Python输出面板以查看完整的错误消息(使用查看>输出菜单命令显示输出面板,然后从右侧的下拉菜单中选择Python)。
一旦 VS Code 识别到测试,它将提供几种运行这些测试的方法,如 运行测试 中所述。
运行测试
您可以使用以下任何操作来运行测试
-
在打开测试文件的情况下,选择显示在测试定义行旁边的代码行中的绿色运行图标,如上一节所示。此命令仅运行该方法。
-
从命令面板运行以下任何命令
- 测试:运行所有测试 - 运行所有已发现的测试。
- 测试:运行当前文件中的测试 - 运行编辑器中打开的文件中的所有测试。
- 测试:运行光标处的测试 - 仅运行编辑器中光标下的测试方法。
-
从测试资源管理器
-
要运行所有发现的测试,请选择测试资源管理器顶部的播放按钮
-
要运行特定组的测试或单个测试,请选择文件、类或测试,然后选择该项目右侧的播放按钮
-
您还可以通过测试资源管理器运行一组测试。为此,请在要运行的测试上Ctrl+单击(或 macOS 上的Cmd+单击),右键单击其中一个测试,然后选择运行测试。
-
测试运行后,VS Code 会将结果直接显示在编辑器中,作为代码行装饰。失败的测试也会在编辑器中突出显示,其中包含一个显示测试运行错误消息和所有测试运行历史记录的快速查看视图。您可以按Escape关闭该视图,也可以通过打开用户设置(首选项:打开设置(UI)命令,在命令面板中)并将测试:自动打开快速查看视图设置的值更改为never
来禁用它。
在测试资源管理器中,将显示单个测试以及包含这些测试的任何类和文件的测试结果。如果该文件夹中的任何测试未通过,则文件夹将显示一个失败图标。
VS Code 还将在Python 测试日志输出面板中显示测试结果。
并行运行测试
通过 pytest-xdist
包,支持使用 pytest 并行运行测试。要启用并行测试
-
打开集成终端并安装
pytest-xdist
包。有关更多详细信息,请参阅项目的文档页面。对于 Windows
py -3 -m pip install pytest-xdist
对于 macOS/Linux
python3 -m pip install pytest-xdist
-
接下来,在您的项目目录中创建一个名为
pytest.ini
的文件,并添加以下内容,指定要使用的 CPU 数量。例如,要将其设置为 4 个 CPU[pytest] addopts=-n4
或者,如果您使用的是
pyproject.toml
文件[tool.pytest.ini_options] addopts="-n 4"
-
运行您的测试,这些测试现在将并行运行。
使用覆盖率运行测试
测试覆盖率是指您的代码中有多少被您的测试覆盖,这可以帮助您识别未被充分测试的代码区域。有关测试覆盖率的更多信息,请访问 VS Code 的测试覆盖率文档。
提示:目前仅在将
"python.experiments.optInto": ["pythonTestAdapter"]
添加到您的用户settings.json
中时,才支持使用覆盖率运行 Python 测试。
要使用启用了覆盖率的测试运行测试,请选择测试资源管理器中的覆盖率运行图标,或从您通常触发测试运行的任何菜单中选择“使用覆盖率运行”选项。如果您使用的是 pytest,Python 扩展将使用pytest-cov
插件运行覆盖率,或者对于 unittest 使用coverage.py
。
注意:在使用覆盖率运行测试之前,请确保为您的项目安装了正确的测试覆盖率包。
覆盖率运行完成后,编辑器中将突出显示行级覆盖率的行。测试覆盖率结果将显示在测试资源管理器中的“测试覆盖率”子选项卡中,您也可以通过在命令面板(F1)
中使用测试:专注于测试覆盖率视图导航到该子选项卡。在这个面板上,您可以查看工作区中每个文件和文件夹的行覆盖率指标。
为了更细致地控制使用 pytest 时的覆盖率运行,您可以编辑 python.testing.pytestArgs
设置以包含您的规格。当 python.testing.pytestArgs
中存在 pytest 参数 --cov
时,Python 扩展不会对覆盖率参数进行任何其他编辑,以使您的自定义生效。如果没有找到 --cov
参数,扩展将在运行之前将 --cov=.
添加到 pytest 参数中,以在工作区根目录启用覆盖率。
调试测试
您可能偶尔需要在调试器中逐步执行和分析测试,可能是因为测试本身存在代码缺陷,需要追踪,或者为了更好地理解为什么正在测试的代码区域会失败。有关调试的更多信息或了解它在 VS Code 中的工作原理,您可以阅读Python 调试配置 和一般 VS Code 调试 文章。
例如,前面给出的 test_decrement
函数失败是因为断言本身有错误。以下步骤演示如何分析测试
-
在
test_decrement
函数中的第一行设置断点。 -
右键单击函数定义旁边的代码行装饰,然后选择调试测试,或者选择测试资源管理器中该测试旁边的调试测试图标。VS Code 启动调试器,并在断点处暂停。
-
在调试控制台面板中,输入
inc_dec.decrement(3)
以查看实际结果为 2,而测试中指定的预期结果为错误值 4。 -
停止调试器并更正有缺陷的代码
# unittest self.assertEqual(inc_dec.decrement(3), 2) # pytest assert inc_dec.decrement(3) == 2
-
保存文件并再次运行测试以确认它们通过,并查看代码行装饰也指示通过状态。
注意:运行或调试测试不会自动保存测试文件。始终确保在运行测试之前保存对测试的更改,否则您可能会对结果感到困惑,因为结果仍然反映了文件的先前版本!
您可以从命令面板中使用以下命令来调试测试
- 测试:调试所有测试 - 启动工作区中所有测试的调试器。
- 测试:调试当前文件中的测试 - 启动您在编辑器中打开的文件中定义的测试的调试器。
- 测试:调试光标处的测试 - 仅启动您在编辑器中将光标聚焦在其上的方法的调试器。您还可以使用测试资源管理器中的调试测试图标,为选定范围内的所有测试和所有发现的测试启动调试器。
您还可以通过将 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 测试。
提示:Django 测试支持目前仅在将
"python.experiments.optInto": ["pythonTestAdapter"]
添加到您的用户settings.json
时才受支持。
- 在您的
settings.json
文件 中设置"python.testing.unittestEnabled": true,
。 - 将
MANAGE_PY_PATH
添加为环境变量。- 在项目的根目录中创建一个
.env
文件。 - 将
MANAGE_PY_PATH='<path-to-manage.py>'
添加到.env
文件中,将<path-to-manage.py>
替换为应用程序的manage.py
文件的路径。提示:您可以在资源管理器视图中右键单击文件并选择 **复制路径** 来复制路径。
- 在项目的根目录中创建一个
- 根据需要将 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
环境变量时使用manage.py
文件的绝对路径。
pytest 的 IntelliSense
Pylance 提供 IntelliSense 功能,可以帮助您更有效地使用 pytest 固定装置 和 参数化测试。
在为测试函数键入参数时,Pylance 将为您提供 完成 列表,其中包括来自 @pytest.mark.parametrize
装饰器的参数名称,以及在您的测试文件中或 conftest.py
中定义的现有 pytest 固定装置。还支持 代码导航 功能,例如 **转到定义** 和 **查找所有引用** 以及 重命名符号重构。
将鼠标悬停在固定装置引用或参数化参数引用上时,Pylance 将显示推断的类型注释,这些注释基于固定装置的返回值或基于传递给参数化装饰器的参数的推断类型。
Pylance 还提供 代码操作 以将类型注释添加到具有固定装置参数的测试函数。推断的固定装置参数类型的内联提示也可以通过将您的用户设置中的 python.analysis.inlayHints.pytestParameters
设置为 true
来启用。
测试配置设置
使用 Python 进行测试的行为由 VS Code 提供的一般 UI 设置以及特定于 Python 和您启用的任何框架的设置驱动。
一般 UI 设置
影响测试功能的 UI 的设置由 VS Code 本身提供,您可以在搜索“测试”时在 VS Code 设置编辑器 中找到这些设置。
一般 Python 设置
设置 (python.testing.) |
默认 | 描述 |
---|---|---|
autoTestDiscoverOnSaveEnabled | true |
指定是否在保存测试文件时启用或禁用自动运行测试发现。您可能需要在对该设置进行更改后重新加载窗口,才能应用该设置。 |
cwd | null | 为测试指定可选的工作目录。 |
debugPort | 3000 |
用于调试 unittest 测试的端口号。 |
promptToConfigure | true |
指定是否在发现潜在测试时,VS Code 会提示配置测试框架。 |
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 配置 中所述的 pytest.ini
文件来配置 pytest。
注意:如果您安装了 pytest-cov 覆盖模块,VS Code 在调试时不会在断点处停止,因为 pytest-cov 使用相同技术来访问正在运行的源代码。为了防止这种行为,在调试测试时,请在
pytestArgs
中包含--no-cov
,例如通过将"env": {"PYTEST_ADDOPTS": "--no-cov"}
添加到您的调试配置中。(请参阅上面的 调试测试,了解如何设置该启动配置。)(有关更多信息,请参阅 pytest-cov 文档中的 调试器和 PyCharm。)
IntelliSense 设置
IntelliSense 设置 (python.analysis.) |
默认 | 描述 |
---|---|---|
inlayHints.pytestParameters | false | 是否显示 pytest 固定装置参数类型的内联提示。接受的值为 true 或 false 。 |