pytest-2.3:fixture/funcarg 演化的原因¶
目标受众:阅读本文档需要具备 python 测试、xUnit 设置方法和(以前的)基本 pytest funcarg 机制的基本知识,请参阅 funcargs 和 pytest_funcarg__。如果您是 pytest 新手,则可以简单地忽略此部分并阅读其他部分。
先前 pytest_funcarg__
机制的不足之处¶
在每次需要测试函数的 funcarg 时,pytest-2.3 之前的 funcarg 机制都会调用一个工厂。如果一个工厂希望在不同作用域中重复使用资源,它通常会使用 request.cached_setup()
帮助器来管理资源缓存。以下是一个基本示例,说明我们如何实现一个按会话划分的 Database 对象
# content of conftest.py
class Database:
def __init__(self):
print("database instance created")
def destroy(self):
print("database instance destroyed")
def pytest_funcarg__db(request):
return request.cached_setup(
setup=DataBase, teardown=lambda db: db.destroy, scope="session"
)
这种方法存在一些限制和困难
作用域 funcarg 资源创建并不直接,相反,必须了解复杂的 cached_setup() 方法机制。
对“db”资源进行参数化并不直接:您需要应用“parametrize”装饰器或实现一个
pytest_generate_tests
钩子,调用parametrize()
,它在使用资源的地方执行参数化。此外,您需要修改工厂以使用extrakey
参数,其中包含request.param
到Request.cached_setup
调用的参数。多个参数化会话范围资源将同时处于活动状态,这使得它们难以影响被测应用程序的全局状态。
您无法在 xUnit 设置方法中使用 funcarg 工厂。
如果未在测试函数签名中声明,则非参数化固件函数无法使用参数化 funcarg 资源。
所有这些限制都在 pytest-2.3 及其改进的 fixture 机制 中得到解决。
fixture/funcarg 工厂的直接作用域¶
您可以使用 @pytest.fixture 装饰器并直接声明作用域,而不是使用缓存作用域调用 cached_setup()
@pytest.fixture(scope="session")
def db(request):
# factory will only be invoked once per session -
db = DataBase()
request.addfinalizer(db.destroy) # destroy when session is finished
return db
此工厂实现不再需要调用 cached_setup()
,因为它只会每会话调用一次。此外, request.addfinalizer()
根据工厂函数操作的指定资源作用域注册一个终结器。
funcarg 资源工厂的直接参数化¶
以前,funcarg 工厂不能直接导致参数化。您需要在测试函数上指定 @parametrize
装饰器或实现 pytest_generate_tests
钩子来执行参数化,即使用不同的值集多次调用测试。pytest-2.3 为在工厂本身上使用装饰器引入了装饰器
@pytest.fixture(params=["mysql", "pg"])
def db(request): ... # use request.param
这里将调用工厂两次(将相应的“mysql”和“pg”值设置为 request.param
属性),并且所有需要“db”的测试也将运行两次。“mysql”和“pg”值也将用于报告测试调用变体。
这种参数化 funcarg 工厂的新方法在许多情况下允许重复使用已编写的工厂,因为实际上 request.param
已在通过 metafunc.parametrize(indirect=True)
调用对测试函数/类进行参数化时使用。
当然,将参数化和作用域结合起来完全没问题
@pytest.fixture(scope="session", params=["mysql", "pg"])
def db(request):
if request.param == "mysql":
db = MySQL()
elif request.param == "pg":
db = PG()
request.addfinalizer(db.destroy) # destroy when session is finished
return db
这将执行所有需要按会话划分的“db”资源的测试两次,接收由对工厂函数的两次相应调用创建的值。
使用 @fixture 装饰器时没有 pytest_funcarg__
前缀¶
使用 @fixture
装饰器时,函数的名称表示可以作为函数参数访问资源的名称
@pytest.fixture()
def db(request): ...
可以请求 funcarg 资源的名称是 db
。
您仍然可以使用“旧的”非装饰器方式指定 funcarg 工厂,即
def pytest_funcarg__db(request): ...
但是,这样无法定义作用域和参数化。因此,建议使用工厂装饰器。
解决每个会话设置/自动使用固定装置¶
pytest 长期提供 pytest_configure 和 pytest_sessionstart 钩子,它们通常用于设置全局资源。这会产生几个问题
在分布式测试中,管理进程会设置永远不需要的测试资源,因为它只协调工作进程的测试运行活动。
如果您只执行收集(使用“–collect-only”),仍然会执行资源设置。
如果 pytest_sessionstart 包含在某些子目录 conftest.py 文件中,则不会调用它。这是因为此钩子实际上用于报告,特别是带有平台/自定义信息的测试头。
此外,除了实现 pytest_runtest_setup()
钩子并自己处理作用域/缓存之外,很难从插件或 conftest 文件定义作用域设置。在测试执行期间调用 pytest_runtest_setup()
,并且在收集时进行参数化,因此使用参数化几乎不可能做到这一点。
因此,pytest_configure/session/runtest_setup 通常不适合实现常见的固定装置需求。因此,pytest-2.3 引入了 自动使用固定装置(您不必请求的固定装置),它与通用的 固定装置机制 完全集成,并淘汰了以前对 pytest 钩子的许多使用。
funcargs/固定装置发现现在在收集时发生¶
从 pytest-2.3 开始,固定装置/funcarg 工厂的发现将在收集时进行处理。这更有效,特别是对于大型测试套件。此外,“pytest –collect-only”的调用应该能够在将来显示大量设置信息,因此提供了一种不错的方法来概述项目中的固定装置管理。
结论和兼容性说明¶
funcargs 最初引入到 pytest-2.0 中。在 pytest-2.3 中,该机制得到了扩展和完善,现在被描述为固定装置
以前,funcarg 工厂使用特殊的
pytest_funcarg__NAME
前缀指定,而不是使用@pytest.fixture
装饰器。工厂收到一个
request
对象,该对象通过request.cached_setup()
调用管理缓存,并允许通过request.getfuncargvalue()
调用使用其他 funcargs。这些复杂的 API 使得很难进行适当的参数化和实现资源缓存。新的pytest.fixture()
装饰器允许声明作用域并让 pytest 为您解决问题。如果您使用了参数化和使用
request.cached_setup()
的 funcarg 工厂,建议花几分钟时间简化固定装置函数代码,以使用 固定装置参考 装饰器。这也将允许利用每个资源组的自动测试。