使用和调用

通过调用pytest python -m pytest

您可以从命令行通过python解释器调用测试:

python -m pytest [...]

这几乎等同于调用命令行脚本 pytest [...] 直接,除了通过 python 还将当前目录添加到 sys.path .

可能的出口代码

运行 pytest 可能导致六种不同的退出代码:

退出代码0

所有测试都已收集并成功通过

退出代码1

测试已收集并运行,但有些测试失败

退出代码2

测试执行被用户中断

退出代码3

执行测试时发生内部错误

退出代码4

pytest命令行使用错误

退出代码5

未收集任何测试

他们的代表是 pytest.ExitCode 枚举。作为公共API的一部分的出口代码可以通过以下方式直接导入和访问:

from pytest import ExitCode

注解

如果您想在某些情况下自定义退出代码,特别是在没有收集测试的情况下,请考虑使用 pytest-custom_exit_code 插件。

获取有关版本、选项名称、环境变量的帮助

pytest --version   # shows where pytest was imported from
pytest --fixtures  # show available builtin function arguments
pytest -h | --help # show help on command line and config file options

完整的命令行标志可以在 reference .

第一次(或n次)故障后停止

在第一(n)次失败后停止测试过程:

pytest -x           # stop after first failure
pytest --maxfail=2  # stop after two failures

指定测试/选择测试

pytest支持几种方法来运行和从命令行中选择测试。

在模块中运行测试

pytest test_mod.py

在目录中运行测试

pytest testing/

按关键字表达式运行测试

pytest -k "MyClass and not method"

这将运行包含与给定名称匹配的名称的测试 字符串表达式 (不区分大小写),它可以包括使用文件名、类名和函数名作为变量的Python运算符。上面的例子将运行 TestMyClass.test_something 但不是 TestMyClass.test_method_simple .

按节点ID运行测试

每个收集的测试都被分配一个唯一的 nodeid 它由模块文件名和诸如类名、函数名和参数化参数等说明符组成,用 :: 字符。

在模块内运行特定测试:

pytest test_mod.py::test_func

在命令行中指定测试方法的另一个示例:

pytest test_mod.py::TestClass::test_method

Run tests by marker expressions

pytest -m slow

将运行所有用 @pytest.mark.slow 装饰符。

有关详细信息,请参阅 marks .

从包运行测试

pytest --pyargs pkg.testing

这将导入 pkg.testing 并使用其文件系统位置来查找和运行测试。

修改python回溯打印

修改回溯打印的示例:

pytest --showlocals # show local variables in tracebacks
pytest -l           # show local variables (shortcut)

pytest --tb=auto    # (default) 'long' tracebacks for the first and last
                     # entry, but 'short' style for the other entries
pytest --tb=long    # exhaustive, informative traceback formatting
pytest --tb=short   # shorter traceback format
pytest --tb=line    # only one line per failure
pytest --tb=native  # Python standard library formatting
pytest --tb=no      # no traceback at all

这个 --full-trace 导致在出错时打印很长的跟踪(比 --tb=long )它还确保在 KeyboardInterrupt (CTRL+C)。如果测试花费的时间太长,并且您使用ctrl+c中断它们以找出测试的位置,那么这非常有用。 悬挂 . 默认情况下,不会显示任何输出(因为键盘中断被pytest捕获)。通过使用此选项,可以确保显示跟踪。

详细总结报告

这个 -r 标记可用于在测试会话结束时显示“简短测试摘要信息”,使大型测试套件中的所有故障、跳过、xfails等的清晰图像变得容易。

它默认为 fE 列出失败和错误。

例子:

# content of test_example.py
import pytest


@pytest.fixture
def error_fixture():
    assert 0


def test_ok():
    print("ok")


def test_fail():
    assert 0


def test_error(error_fixture):
    pass


def test_skip():
    pytest.skip("skipping this test")


def test_xfail():
    pytest.xfail("xfailing this test")


@pytest.mark.xfail(reason="always xfail")
def test_xpass():
    pass
$ pytest -ra
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 6 items

test_example.py .FEsxX                                               [100%]

================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________

    @pytest.fixture
    def error_fixture():
>       assert 0
E       assert 0

test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________

    def test_fail():
>       assert 0
E       assert 0

test_example.py:14: AssertionError
========================= short test summary info ==========================
SKIPPED [1] test_example.py:22: skipping this test
XFAIL test_example.py::test_xfail
  reason: xfailing this test
XPASS test_example.py::test_xpass always xfail
ERROR test_example.py::test_error - assert 0
FAILED test_example.py::test_fail - assert 0
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===

这个 -r 选项接受其后面的字符数,使用 a 上面的意思是“除通行证外的所有通行证”。

以下是可用字符的完整列表:

  • f -失败

  • E -误差

  • s 跳过

  • x -失败

  • X -XPASS

  • p 通过

  • P -通过输出

用于(取消)选择组的特殊字符:

  • a - all except pP

  • A -所有

  • N -无,这不能用来显示任何内容(因为 fE 是默认设置)

可以使用多个字符,例如,要只查看失败和跳过的测试,可以执行:

$ pytest -rfs
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 6 items

test_example.py .FEsxX                                               [100%]

================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________

    @pytest.fixture
    def error_fixture():
>       assert 0
E       assert 0

test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________

    def test_fail():
>       assert 0
E       assert 0

test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail - assert 0
SKIPPED [1] test_example.py:22: skipping this test
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===

使用 p 列出通过的测试,同时 P 添加一个额外的“通过”部分,其中包含那些通过但已捕获输出的测试:

$ pytest -rpP
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR
collected 6 items

test_example.py .FEsxX                                               [100%]

================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________

    @pytest.fixture
    def error_fixture():
>       assert 0
E       assert 0

test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________

    def test_fail():
>       assert 0
E       assert 0

test_example.py:14: AssertionError
================================== PASSES ==================================
_________________________________ test_ok __________________________________
--------------------------- Captured stdout call ---------------------------
ok
========================= short test summary info ==========================
PASSED test_example.py::test_ok
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===

下降到 PDB (python debugger)失败时

python附带一个内置的python调试器,名为 PDB. pytest 允许一个人掉进 PDB 通过命令行选项提示:

pytest --pdb

这将在每次失败(或键盘中断)时调用Python调试器。通常,您可能只想在第一次失败测试中这样做,以了解特定的失败情况:

pytest -x --pdb   # drop to PDB on first failure, then end test session
pytest --pdb --maxfail=3  # drop to PDB for first three failures

请注意,在任何失败时,异常信息都存储在 sys.last_valuesys.last_typesys.last_traceback . 在交互使用中,这允许使用任何调试工具进入后期调试。还可以手动访问异常信息,例如:

>>> import sys
>>> sys.last_traceback.tb_lineno
42
>>> sys.last_value
AssertionError('assert result == "ok"',)

下降到 PDB (python调试器)在测试开始时

pytest 允许一个人掉进 PDB 通过命令行选项在每次测试开始时立即提示:

pytest --trace

这将在每次测试开始时调用Python调试器。

设置断点

要在代码中设置断点,请使用本机python import pdb;pdb.set_trace() 调用代码,pytest会自动禁用该测试的输出捕获:

  • 其他测试中的输出捕获不受影响。

  • 任何先前的测试输出已经被捕获并将被处理。

  • 结束调试器会话(通过 continue 命令)。

使用内置断点函数

python 3.7引入了一个内置的 breakpoint() 功能。pytest支持使用 breakpoint() 有以下行为:

  • 什么时候? breakpoint() 被称为 PYTHONBREAKPOINT 设置为默认值时,Pytest将使用自定义内部PDB跟踪UI而不是系统默认值 Pdb .

  • 测试完成后,系统将默认返回系统。 Pdb 跟踪用户界面。

  • --pdb 传递到pytest时,自定义内部PDB跟踪UI与 breakpoint() 以及失败的测试/未处理的异常。

  • --pdbcls 可用于指定自定义调试器类。

分析测试执行持续时间

在 6.0 版更改.

要获取长度超过1.0秒的最慢10个测试持续时间的列表:

pytest --durations=10 --durations-min=1.0

默认情况下,pytest不会显示太小的测试持续时间(<0.005s),除非 -vv 在命令行上传递。

故障处理程序

5.0 新版功能.

这个 faulthandler 标准模块可用于在segfault或超时后转储python跟踪。

模块将自动启用pytest运行,除非 -p no:faulthandler 在命令行上给出。

同时 faulthandler_timeout=X 如果测试时间超过 X 完成时间(在Windows上不可用)。

注解

此功能已从外部集成 pytest-faulthandler 插件,有两个小的区别:

  • 要禁用它,请使用 -p no:faulthandler 而不是 --no-faulthandler :前者可以与任何插件一起使用,因此它保存了一个选项。

  • 这个 --faulthandler-timeout 命令行选项已成为 faulthandler_timeout 配置选项。它仍然可以从命令行配置,使用 -o faulthandler_timeout=X .

Warning about unraisable exceptions and unhandled thread exceptions

6.2 新版功能.

注解

这些功能仅适用于Python>=3.8。

未处理的异常是在无法传播到调用方的情况下引发的异常。最常见的情况是在 __del__ 实施。

未处理的线程异常是在 Thread 但未处理,导致线程不干净地终止。

这两种类型的异常通常都被认为是bug,但可能不会被注意到,因为它们不会导致程序本身崩溃。Pytest检测到这些情况,并发出在测试运行摘要中可见的警告。

插件将自动启用pytest运行,除非 -p no:unraisableexception (适用于不能提出要求的例外情况)及 -p no:threadexception (对于线程异常)选项在命令行上给出。

警告可以有选择地使用 pytest.mark.filterwarnings 马克。警告类别为 pytest.PytestUnraisableExceptionWarningpytest.PytestUnhandledThreadExceptionWarning .

创建JUnitXML格式文件

创建可被读取的结果文件 Jenkins 或其他持续集成服务器,使用此调用:

pytest --junitxml=path

在以下位置创建XML文件 path .

要设置根测试套件XML项的名称,可以配置 junit_suite_name 配置文件中的选项:

[pytest]
junit_suite_name = my_suite

4.0 新版功能.

JUnit XML规范似乎表明 "time" 属性应该报告总的测试执行时间,包括安装和拆卸 (12 )它是默认的pytest行为。要只报告调用持续时间,请配置 junit_duration_report 这样的选择:

[pytest]
junit_duration_report = call

record_property

如果要记录测试的其他信息,可以使用 record_property 固定装置:

def test_function(record_property):
    record_property("example_key", 1)
    assert True

这将添加一个额外属性 example_key="1" 到生成的 testcase 标签:

<testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009">
  <properties>
    <property name="example_key" value="1" />
  </properties>
</testcase>

或者,您可以将此功能与自定义标记集成:

# content of conftest.py


def pytest_collection_modifyitems(session, config, items):
    for item in items:
        for marker in item.iter_markers(name="test_id"):
            test_id = marker.args[0]
            item.user_properties.append(("test_id", test_id))

在你的测试中:

# content of test_function.py
import pytest


@pytest.mark.test_id(1501)
def test_function():
    assert True

将导致:

<testcase classname="test_function" file="test_function.py" line="0" name="test_function" time="0.0009">
  <properties>
    <property name="test_id" value="1501" />
  </properties>
</testcase>

警告

请注意,使用此功能将中断对最新JUnitXML架构的架构验证。当与某些CI服务器一起使用时,这可能是一个问题。

record_xml_attribute

要向testcase元素添加额外的xml属性,可以使用 record_xml_attribute 固定装置。这也可用于覆盖现有值:

def test_function(record_xml_attribute):
    record_xml_attribute("assertions", "REQ-1234")
    record_xml_attribute("classname", "custom_classname")
    print("hello world")
    assert True

不像 record_property ,这不会添加新的子元素。相反,这将添加一个属性 assertions="REQ-1234" 在生成的 testcase 标记并重写默认值 classname 具有 "classname=custom_classname"

<testcase classname="custom_classname" file="test_function.py" line="0" name="test_function" time="0.003" assertions="REQ-1234">
    <system-out>
        hello world
    </system-out>
</testcase>

警告

record_xml_attribute 是一个实验性的特性,它的接口在未来的版本中可能会被更强大和通用的东西所取代。然而,功能本身将保持不变。

使用此 record_xml_property 在使用CI工具分析XML报告时可以提供帮助。但是,一些解析器对允许的元素和属性非常严格。许多工具使用XSD模式(如下面的示例)来验证传入的XML。确保您使用的是解析器允许的属性名。

下面是Jenkins用于验证XML报告的方案:

<xs:element name="testcase">
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="skipped" minOccurs="0" maxOccurs="1"/>
            <xs:element ref="error" minOccurs="0" maxOccurs="unbounded"/>
            <xs:element ref="failure" minOccurs="0" maxOccurs="unbounded"/>
            <xs:element ref="system-out" minOccurs="0" maxOccurs="unbounded"/>
            <xs:element ref="system-err" minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
        <xs:attribute name="name" type="xs:string" use="required"/>
        <xs:attribute name="assertions" type="xs:string" use="optional"/>
        <xs:attribute name="time" type="xs:string" use="optional"/>
        <xs:attribute name="classname" type="xs:string" use="optional"/>
        <xs:attribute name="status" type="xs:string" use="optional"/>
    </xs:complexType>
</xs:element>

警告

请注意,使用此功能将中断对最新JUnitXML架构的架构验证。当与某些CI服务器一起使用时,这可能是一个问题。

record_testsuite_property

4.5 新版功能.

如果要在测试套件级别添加属性节点,该节点可能包含与所有测试相关的属性,则可以使用 record_testsuite_property 会话范围的夹具:

这个 record_testsuite_property 会话范围的fixture可用于添加与所有测试相关的属性。

import pytest


@pytest.fixture(scope="session", autouse=True)
def log_global_env_facts(record_testsuite_property):
    record_testsuite_property("ARCH", "PPC")
    record_testsuite_property("STORAGE_TYPE", "CEPH")


class TestMe:
    def test_foo(self):
        assert True

夹具是一个可调用的 namevalue A的 <property> 在生成的XML的测试套件级别添加的标记:

<testsuite errors="0" failures="0" name="pytest" skipped="0" tests="1" time="0.006">
  <properties>
    <property name="ARCH" value="PPC"/>
    <property name="STORAGE_TYPE" value="CEPH"/>
  </properties>
  <testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/>
</testsuite>

name 必须是字符串, value 将转换为字符串并正确进行XML转义。

生成的XML与最新的 xunit 标准,与 record_propertyrecord_xml_attribute .

创建结果日志格式文件

要创建纯文本机器可读的结果文件,可以发出:

pytest --resultlog=path

看看里面的内容 path 位置。此类文件由 PyPy-test 显示多个修订版的测试结果的网页。

警告

此选项很少使用,并计划在pytest 6.0中删除。

如果使用此选项,请考虑使用新的 pytest-reportlog 而是插件。

the deprecation docs 更多信息。

向在线Pastebin服务发送测试报告

为每个测试失败创建URL

pytest --pastebin=failed

这将向远程粘贴服务提交测试运行信息,并为每个失败提供一个URL。您可以像往常一样选择测试或添加例如 -x 如果你只想发送一个特定的失败。

为整个测试会话日志创建URL

pytest --pastebin=all

当前仅实现粘贴到http://bpaste.net服务。

在 5.2 版更改.

如果由于任何原因创建URL失败,将生成一个警告,而不是使整个测试套件失败。

早期加载插件

您可以在命令行中通过 -p 选项:

pytest -p mypluginmodule

选项接收 name 参数,可以是:

  • 例如,完整的模块点状名称 myproject.plugins . 此虚线名称必须是可导入的。

  • 插件的入口点名称。这是传递给 setuptools when the plugin is registered. For example to early-load the pytest-cov 您可以使用的插件:

    pytest -p pytest_cov
    

禁用插件

要在调用时禁用加载特定插件,请使用 -p 选项和前缀 no: .

示例:禁用加载插件 doctest ,负责从文本文件执行doctest测试,调用pytest如下:

pytest -p no:doctest

从python代码调用pytest

你可以调用 pytest 直接从Python代码:

pytest.main()

这就像从命令行调用“pytest”。它不会上升 SystemExit 但返回exitcode。您可以传入选项和参数:

pytest.main(["-x", "mytestdir"])

您可以指定其他插件到 pytest.main

# content of myinvoke.py
import pytest


class MyPlugin:
    def pytest_sessionfinish(self):
        print("*** test run reporting finishing")


pytest.main(["-qq"], plugins=[MyPlugin()])

运行它会显示 MyPlugin 添加并调用其挂钩:

$ python myinvoke.py
.FEsxX.                                                              [100%]*** test run reporting finishing

================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________

    @pytest.fixture
    def error_fixture():
>       assert 0
E       assert 0

test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________

    def test_fail():
>       assert 0
E       assert 0

test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail - assert 0
ERROR test_example.py::test_error - assert 0

注解

调用 pytest.main() 将导致导入您的测试和它们导入的任何模块。由于python的导入系统具有缓存机制,因此随后调用 pytest.main() 来自同一进程的不会反映调用之间对这些文件的更改。因此,多次调用 pytest.main() 不建议使用同一进程(例如为了重新运行测试)。