在Visual Studio Code中进行Python测试

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

单元测试的一些背景

(如果您已经熟悉单元测试,可以跳过直接查看演练。)

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

例如,假设你有一个函数来验证用户在网页表单中输入的账号格式:

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,你还会被要求选择用于识别测试文件的文件通配符模式。

注意: 文件通配符模式是一种定义的字符串模式,它基于通配符匹配文件或文件夹名称,以决定是否包含它们。

当测试未配置时,测试资源管理器中显示的配置Python测试按钮。

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

如果两个框架都启用,那么Python扩展将只运行pytest

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

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测试的VS Code测试资源管理器

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

在测试资源管理器中显示的发现失败错误消息

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

运行测试

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

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

    当测试文件在编辑器中打开时,在装订线上显示的运行测试图标

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

    • Test: Run All Tests - Runs all tests that have been discovered.
    • Test: Run Tests in Current File - Runs all tests in a file that that is open in the editor.
    • Test: Run Test at Cursor - Runs only the test method under your cursor in the editor.
  • 测试资源管理器中:

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

      通过测试资源管理器运行所有测试

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

      通过测试资源管理器在特定范围内运行测试

    • 你也可以通过测试资源管理器运行选定的测试。要做到这一点,Ctrl+点击(或在macOS上Cmd+点击)你想要运行的测试,右键点击其中一个,然后选择运行测试

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

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

单元测试类和测试资源管理器中的测试结果

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

Python测试日志输出面板中的测试结果

并行运行测试

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

  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的测试覆盖率文档

提示: 目前只有在您的用户settings.json中添加了"python.experiments.optInto": ["pythonTestAdapter"]时,才支持使用覆盖率运行Python测试。

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

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

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

Gif显示使用覆盖率运行Python测试。

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

调试测试

你可能偶尔需要在调试器中逐步执行和分析测试,这可能是因为测试本身存在需要追踪的代码缺陷,或者是为了更好地理解被测试代码区域为何失败。有关调试的更多信息或了解其在VS Code中的工作原理,你可以阅读Python调试配置和一般的VS Code调试文章。

例如,前面给出的test_decrement函数失败是因为断言本身有问题。以下步骤展示了如何分析测试:

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

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

    测试资源管理器中的调试测试图标

  3. 调试控制台面板中,输入inc_dec.decrement(3)可以看到实际结果是2,而测试中指定的预期结果是错误的值4。

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

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

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

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

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

你也可以通过在你的settings.json文件中将testing.defaultGutterClickAction设置值更改为debug,来更改点击装饰槽的默认行为,以调试测试而不是运行。

调试器在测试中的工作方式与其他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扩展支持的所有测试命令。这些都可以通过命令面板找到:

Command Name Description
Python: Configure Tests Configure the test framework to be used with the Python extension.
Test: Clear All Results Clear all tests statuses, as the UI persists test results across sessions.
Test: Debug Failed Tests Debug tests that failed in the most recent test run.
Test: Debug Last Run Debug tests that were executed in the most recent test run.
Test: Debug Test at Cursor Debug the test method where you have your cursor focused on the editor. Similar to Python: Debug Test Method... on versions prior to 2021.9.
Test: Debug Tests in Current File Debug tests in the file that is currently in focus on the editor.
Test: Go to Next Test Failure If the error peek view is open, open and move to the peek view of the next test in the explorer that has failed.
Test: Go to Previous Test Failure If the error peek view is open, open and move to the peek view of the previous test in the explorer that has failed.
Test: Peek Output Opens the error peek view for a test method that has failed.
Test: Refresh Tests Perform test discovery and updates the Test Explorer to reflect any test changes, addition, or deletion. Similar to Python: Discover Tests on versions prior to 2021.9.
Test: Rerun Failed Tests Run tests that failed in the most recent test run. Similar to Python: Run Failed Tests on versions prior to 2021.9.
Test: Rerun Last Run Debug tests that were executed in the most recent test run.
Test: Run All Tests Run all discovered tests. Equivalent to Python: Run All Tests on versions prior to 2021.9.
Test: Run Test at Cursor Run the test method where you have your cursor focused on the editor. Similar to Python: Run Test Method... on versions prior to 2021.9.
Test: Run Test in Current File Run tests in the file that is currently in focus on the editor. Equivalent to Python: Run Current Test File on versions prior to 2021.9.
Test: Show Output Open the output with details of all the test runs. Similar to Python: Show Test Output on versions prior to 2021.9.
Testing: Focus on Test Explorer View Open the Test Explorer view. Similar to Testing: Focus on Python View on versions prior to 2021.9.
Test: Stop Refreshing Tests Cancel test discovery.

Django 单元测试

Python 扩展还支持发现和运行 Django 单元测试!只需几个额外的设置步骤,您就可以发现您的 Django 测试:

提示: 目前只有在您的用户settings.json中添加了"python.experiments.optInto": ["pythonTestAdapter"]时,才支持Django测试。

  1. 在你的settings.json 文件中设置"python.testing.unittestEnabled": true,
  2. Add MANAGE_PY_PATH as an environment variable:
    1. Create a .env file at the root of your project.
    2. Add MANAGE_PY_PATH='<path-to-manage.py>' to the .env file, replacing <path-to-manage.py> with the path to your application's manage.py file.

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

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

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

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

故障排除

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

  • Python输出面板中搜索错误信息。它们可能会提供为什么你的测试没有被发现的线索。

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

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

  • 如果最初使用了相对路径,请在设置MANAGE_PY_PATH环境变量时使用manage.py文件的绝对路径。

pytest 的 IntelliSense

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

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

在Python测试文件中向测试函数传递参数时的自动完成建议。该建议是在conftest.py中定义的fixture。

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

基于传递给参数化装饰器的参数类型的Pylance类型推断。

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

当悬停在带有fixture参数的测试函数上时,添加类型注释的代码操作

测试配置设置

使用Python进行测试的行为由VS Code提供的一般UI设置、特定于Python的设置以及您启用的任何框架的设置驱动。

通用用户界面设置

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

通用Python设置

Setting
(python.testing.)
Default Description
autoTestDiscoverOnSaveEnabled true Specifies whether to enable or disable auto run test discovery when saving a test file. You may need to reload the window after making changes to this setting for it to be applied.
cwd null Specifies an optional working directory for tests.
debugPort 3000 Port number used for debugging of unittest tests.
promptToConfigure true Specifies whether VS Code prompts to configure a test framework if potential tests are discovered.

unittest 配置设置

Unittest setting
(python.testing.)
Default Description
unittestEnabled false Specifies whether unittest is enabled as the test framework. The equivalent setting for pytest should be disabled.
unittestArgs ["-v", "-s", ".", "-p", "*test*.py"] Arguments to pass to unittest, where each element that's separated by a space is a separate item in the list. See below for a description of the defaults.

unittest 的默认参数如下:

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

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

查看unittest命令行界面以获取所有可用选项的完整列表。

pytest 配置设置

pytest setting
(python.testing.)
Default Description
pytestEnabled false Specifies whether pytest is enabled as the test framework. The equivalent setting for unittest should be disabled.
pytestPath "pytest" Path to pytest. Use a full path if pytest is located outside the current environment.
pytestArgs [] Arguments to pass to pytest, where each element that's separated by a space is a separate item in the list. See pytest command-line options.

你也可以使用pytest.ini文件来配置pytest,如pytest配置中所述。

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

IntelliSense 设置

IntelliSense setting
(python.analysis.)
Default Description
inlayHints.pytestParameters false Whether to display inlay hints for pytest fixture argument types. Accepted values are true or false.

另请参阅