历史记录

此页面列出了多年来 pytest 以前版本中已更改的功能或行为。它们保留在此处作为历史记录,以便查看旧代码的用户可以找到与之相关的文档。

标记重组和迭代

在 3.6 版本中更改。

pytest 的标记实现传统上通过简单地更新函数的 __dict__ 属性来累积添加标记。结果,标记会以令人惊讶的方式意外地沿类层次结构传递。此外,检索它们的 API 不一致,因为参数化的标记会与使用 @pytest.mark 装饰器应用的标记以及通过 node.add_marker 添加的标记不同地存储。

这种状态在技术上几乎不可能在不深入了解内部结构的情况下正确使用标记中的数据,从而导致在更高级的用法中出现难以理解的细微错误。

根据标记是如何声明/更改的,用户将获得一个 MarkerInfo,其中可能包含兄弟类中的标记,MarkDecorators(当标记来自参数化或 node.add_marker 调用时),丢弃先前的标记。此外,MarkerInfo 充当单个标记,而实际上它表示对具有相同名称的多个标记的合并视图。

最重要的是,模块、类和函数/方法无法以相同的方式访问标记。事实上,标记只能在函数中访问,即使它们是在类/模块中声明的。

为了解决初始设计中的问题,在 pytest 3.6 中引入了新的 API 来访问标记,提供了 _pytest.nodes.Node.iter_markers() 方法以一致的方式迭代标记并重新设计内部结构,从而解决了初始设计中的大量问题。

更新代码

旧的 Node.get_marker(name) 函数被认为已弃用,因为它返回一个内部 MarkerInfo 对象,其中包含适用于该节点的所有标记的合并名称、*args**kwargs

通常,有两种处理标记的方案

1. 标记相互覆盖。顺序很重要,但你只想将标记视为单个项目。例如,模块级别的 log_level('info') 可以被特定测试的 log_level('debug') 覆盖。

在这种情况下,使用 Node.get_closest_marker(name)

# replace this:
marker = item.get_marker("log_level")
if marker:
    level = marker.args[0]

# by this:
marker = item.get_closest_marker("log_level")
if marker:
    level = marker.args[0]

2. 标记以累加方式组合。例如,skipif(condition) 标记意味着你只想评估它们,顺序甚至无关紧要。你可能想将标记视为此处的一个集合。

在这种情况下,迭代每个标记并分别处理它们的 *args**kwargs

# replace this
skipif = item.get_marker("skipif")
if skipif:
    for condition in skipif.args:
        # eval condition
        ...

# by this:
for skipif in item.iter_markers("skipif"):
    condition = skipif.args[0]
    # eval condition

如果您不确定或有任何疑问,请考虑打开一个问题

缓存插件集成到核心

核心缓存 插件的功能以前作为第三方插件分发,名为 pytest-cache。核心插件在命令行选项和 API 使用方面是兼容的,但您只能存储/接收 JSON 可序列化的测试运行之间的数据。

funcargs 和 pytest_funcarg__

在 2.3 之前的版本中没有 @pytest.fixture 标记,您必须为 fixture 工厂使用一个神奇的 pytest_funcarg__NAME 前缀。这仍然存在并将继续受到支持,但不再作为声明 fixture 函数的主要方式进行宣传。

@pytest.yield_fixture 装饰器

在 2.10 版本之前,为了使用 yield 语句执行清理代码,必须使用 yield_fixture 标记标记 fixture。从 2.10 开始,普通 fixture 可以直接使用 yield,因此 yield_fixture 装饰器不再需要,并被认为已弃用。

[pytest]setup.cfg 中的标题

在 3.0 之前,受支持的部分名称是 [pytest]。由于这可能会与某些 distutils 命令冲突,setup.cfg 文件的推荐部分名称现在是 [tool:pytest]

请注意,对于 pytest.initox.ini 文件,分区名称是 [pytest]

将标记应用于 @pytest.mark.parametrize 参数

在 3.1 版本之前,用于标记值的受支持机制使用以下语法

import pytest


@pytest.mark.parametrize(
    "test_input,expected", [("3+5", 8), ("2+4", 6), pytest.mark.xfail(("6*9", 42))]
)
def test_eval(test_input, expected):
    assert eval(test_input) == expected

这是支持此功能的初始黑客手段,但很快就被证明不完整,对于传递函数或使用相同名称但不同参数应用多个标记时会出错。

旧语法计划在 pytest-4.0 中删除。

@pytest.mark.parametrize 参数名称作为元组

在 2.4 之前的版本中,需要将参数名称指定为元组。这仍然有效,但更简单的 "name1,name2,..." 逗号分隔字符串语法现在首先被推荐,因为它更易于编写且产生的行噪声更少。

setup:现在是“autouse fixture”

在 pytest-2.3 发布之前的开发过程中,使用了名称 pytest.setup,但在发布之前,它被重命名并被移至通用 fixture 机制的一部分,即 Autouse fixtures(无需请求的 fixtures)

条件作为字符串而不是布尔值

在 pytest-2.4 之前,指定 skipif/xfail 条件的唯一方法是使用字符串

import sys


@pytest.mark.skipif("sys.version_info >= (3,3)")
def test_function(): ...

在测试函数设置期间,通过调用 eval('sys.version_info >= (3,0)', namespace) 来评估 skipif 条件。该命名空间包含所有模块全局变量,至少包含 ossys

自 pytest-2.4 以来,布尔条件被认为更可取,因为标记可以在测试模块之间自由导入。使用字符串时,您不仅需要导入标记,还需要导入标记使用的所有变量,这违反了封装。

将条件指定为字符串的原因是 pytest 可以根据条件字符串纯粹报告跳过条件的摘要。对于布尔值条件,您需要指定 reason 字符串。

请注意,字符串条件将继续得到完全支持,如果您不需要跨导入标记,则可以自由使用它们。

pytest.mark.skipif(conditionstring)pytest.mark.xfail(conditionstring) 中评估条件字符串时,将在命名空间词典中进行,该词典的构建方式如下

  • 通过将 sysos 模块以及 pytest config 对象放入其中来初始化命名空间。

  • 使用测试函数的模块全局变量更新表达式应用。

pytest config 对象允许您基于您可能已添加的测试配置值进行跳过

@pytest.mark.skipif("not config.getvalue('db')")
def test_function(): ...

与“布尔条件”等效的是

@pytest.mark.skipif(not pytest.config.getvalue("db"), reason="--db was not specified")
def test_function():
    pass

注意

您不能在 pytest 的参数解析发生之前导入的代码中使用 pytest.config.getvalue()。例如,conftest.py 文件在命令行解析之前导入,因此 config.getvalue() 不会正确执行。

pytest.set_trace()

在版本 2.4 之前,要在代码中设置断点,需要使用 pytest.set_trace()

import pytest


def test_function():
    ...
    pytest.set_trace()  # invoke PDB debugger and tracing

这不再需要,并且可以直接使用本机 import pdb;pdb.set_trace() 调用。

有关更多详细信息,请参见 设置断点

“compat” 属性

通过 Node 实例访问 ModuleFunctionClassInstanceFileItem 已长期记录为已弃用,但从 pytest 3.9 开始发出警告。

用户只需 import pytest 并使用 pytest 模块访问这些对象。