pytest-2.3:fixture/funcarg 演进的原因¶
目标受众:阅读本文档需要 Python 测试、xUnit setup 方法和(之前的)基本 pytest funcarg 机制的基本知识,请参阅 funcargs 和 pytest_funcarg__。如果您是 pytest 新手,则可以忽略此部分并阅读其他部分。
先前 pytest_funcarg__
机制的缺点¶
pre pytest-2.3 funcarg 机制在每次需要测试函数的 funcarg 时都会调用工厂。如果工厂想要跨不同作用域重用资源,它通常使用 request.cached_setup()
辅助方法来管理资源的缓存。这是一个基本示例,说明我们如何实现每个 session 的 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
hook,调用parametrize()
,它在资源使用的地方执行参数化。此外,您需要修改工厂以使用包含request.param
的extrakey
参数来调用Request.cached_setup
。多个参数化的 session 作用域资源将同时处于活动状态,使得它们难以影响被测应用程序的全局状态。
无法在 xUnit setup 方法中使用 funcarg 工厂。
如果非参数化的 fixture 函数未在测试函数签名中声明,则无法使用参数化的 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()
,因为它每个 session 只会被调用一次。此外,request.addfinalizer()
根据工厂函数运行的指定资源作用域注册一个 finalizer。
直接参数化 funcarg 资源工厂¶
以前,funcarg 工厂无法直接引起参数化。您需要在测试函数上指定 @parametrize
装饰器或实现 pytest_generate_tests
hook 来执行参数化,即使用不同的值集多次调用测试。 pytest-2.3 引入了一个装饰器,用于工厂本身
@pytest.fixture(params=["mysql", "pg"])
def db(request): ... # use request.param
这里工厂将被调用两次(分别使用设置为 request.param
属性的 “mysql” 和 “pg” 值),并且所有需要 “db” 的测试也将运行两次。“mysql” 和 “pg” 值也将用于报告测试调用变体。
这种新的参数化 funcarg 工厂的方式在许多情况下应该允许重用已编写的工厂,因为当通过 metafunc.parametrize(indirect=True)
调用参数化测试函数/类时,实际上已经使用了 request.param
。
当然,结合参数化和作用域是完全可以的
@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
这将执行所有需要每个 session “db” 资源的测试两次,接收由工厂函数的两次相应调用创建的值。
使用 @fixture 装饰器时没有 pytest_funcarg__
前缀¶
当使用 @fixture
装饰器时,函数的名称表示可以作为函数参数访问资源的名称
@pytest.fixture()
def db(request): ...
可以请求 funcarg 资源的名称是 db
。
您仍然可以使用 “旧” 的非装饰器方式来指定 funcarg 工厂,即
def pytest_funcarg__db(request): ...
但是这样就无法定义作用域和参数化。因此建议使用工厂装饰器。
解决每个 session 的 setup / autouse fixture¶
长期以来,pytest 提供了 pytest_configure
和 pytest_sessionstart
hook,它们通常用于 setup 全局资源。这存在几个问题
在分布式测试中,管理进程会 setup 从不需要的测试资源,因为它只协调 worker 进程的测试运行活动。
如果您只执行收集(使用 “–collect-only”),资源 setup 仍将执行。
如果
pytest_sessionstart
包含在某些子目录 conftest.py 文件中,则不会调用它。这源于该 hook 实际上用于报告,特别是包含平台/自定义信息的测试标头。
此外,从插件或 conftest 文件中定义作用域 setup 并不容易,除了实现 pytest_runtest_setup()
hook 并自己处理作用域/缓存之外。并且几乎不可能使用参数化来实现这一点,因为 pytest_runtest_setup()
在测试执行期间被调用,而参数化发生在收集时。
由此可见,pytest_configure
/session/runtest_setup 通常不适合实现常见的 fixture 需求。因此,pytest-2.3 引入了 Autouse fixture(您不必请求的 fixture),它与通用 fixture 机制 完全集成,并且淘汰了许多先前对 pytest hook 的使用。
funcargs/fixture 发现现在发生在收集时¶
自 pytest-2.3 起,fixture/funcarg 工厂的发现将在收集时处理。这对于大型测试套件来说效率更高。此外,调用 “pytest –collect-only” 将来应该能够显示大量 setup 信息,因此提供了一种很好的方法来概览项目中的 fixture 管理。
结论和兼容性说明¶
funcargs 最初在 pytest-2.0 中引入。在 pytest-2.3 中,该机制得到了扩展和改进,现在被描述为 fixture
以前,funcarg 工厂使用特殊的
pytest_funcarg__NAME
前缀而不是使用@pytest.fixture
装饰器来指定。工厂接收到一个
request
对象,该对象通过request.cached_setup()
调用管理缓存,并允许通过request.getfuncargvalue()
调用使用其他 funcarg。这些复杂的 API 使得难以进行适当的参数化和实现资源缓存。新的pytest.fixture()
装饰器允许声明作用域并让 pytest 为您解决问题。如果您使用参数化和 funcarg 工厂,并且使用了
request.cached_setup()
,建议您花几分钟时间简化您的 fixture 函数代码,以使用 Fixture 参考 装饰器。这也将允许您利用测试的自动按资源分组。