历史记录¶
本页面列出了 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.ini 和 tox.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
这是一个支持该功能的初步 HACK,但很快就被证明是不完整的,对于传递函数或应用具有相同名称但不同参数的多个标记器来说是损坏的。
旧语法计划在 pytest-4.0 中移除。
@pytest.mark.parametrize 参数名称作为元组¶
在 2.4 版本之前,需要将参数名称指定为元组。这仍然有效,但现在首先宣传更简单的 "name1,name2,..." 逗号分隔字符串语法,因为它更容易编写并产生更少的行噪音。
setup: 现在是“自动使用 fixture”¶
在 pytest-2.3 发布之前的开发期间,使用了名称 pytest.setup,但在发布之前,它被重命名并移动成为通用 fixture 机制的一部分,即自动使用 fixture(您无需请求的 fixture)
条件为字符串而不是布尔值¶
在 pytest-2.4 之前,指定 skipif/xfail 条件的唯一方法是使用字符串
import sys
@pytest.mark.skipif("sys.version_info >= (3,3)")
def test_function(): ...
在测试函数设置期间,skipif 条件通过调用 eval('sys.version_info >= (3,0)', namespace) 进行评估。命名空间包含所有模块全局变量,以及至少 os 和 sys。
自 pytest-2.4 以来,布尔条件 被认为是更优的选择,因为标记器可以在测试模块之间自由导入。使用字符串时,您不仅需要导入标记器,还需要导入标记器使用的所有变量,这违反了封装原则。
将条件指定为字符串的原因是 pytest 可以根据条件字符串纯粹报告跳过条件的摘要。使用布尔条件时,您需要指定一个 reason 字符串。
请注意,字符串条件将保持完全支持,如果您不需要交叉导入标记器,可以自由使用它们。
pytest.mark.skipif(conditionstring) 或 pytest.mark.xfail(conditionstring) 中条件字符串的评估在一个命名空间字典中进行,该字典构造如下
命名空间通过将
sys和os模块以及 pytestconfig对象放入其中进行初始化。使用应用表达式的测试函数的模块全局变量进行更新。
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() 调用。
更多详细信息请参阅设置断点。
“兼容”属性¶
通过 Node 实例访问 Module、Function、Class、Instance、File 和 Item 长期以来已被记录为弃用,但从 pytest 3.9 开始发出警告。
用户应该只需 import pytest 并使用 pytest 模块访问这些对象。