pytest-2.3:fixture/funcarg 演进的理由¶
目标受众:阅读本文档需要具备 Python 测试、xUnit 设置方法以及(以前的)基本 pytest funcarg 机制的基础知识,参见 funcargs 和 pytest_funcarg__。如果您是 pytest 新手,则可以忽略此部分并阅读其他部分。
之前 pytest_funcarg__
机制的缺点¶
pytest 2.3 之前的 funcarg 机制在每次需要测试函数的 funcarg 时都会调用一个工厂函数。如果一个工厂函数希望在不同作用域中重用资源,它通常会使用 request.cached_setup()
辅助函数来管理资源缓存。以下是实现一个会话级数据库对象的基本示例
# 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()
,在资源使用的地方执行参数化。此外,你需要修改工厂函数,在Request.cached_setup
调用中使用一个包含request.param
的extrakey
参数。多个参数化的会话级资源将同时激活,这使得它们难以影响被测应用程序的全局状态。
无法在 xUnit 设置方法中使用 funcarg 工厂函数。
如果未在测试函数签名中声明,非参数化的 fixture 函数无法使用参数化的 funcarg 资源。
所有这些限制都已通过 pytest-2.3 及其改进的 fixture 机制得到解决。
fixture/funcarg 工厂函数的直接作用域划分¶
无需使用缓存作用域调用 cached_setup(),你可以使用 @pytest.fixture 装饰器并直接声明作用域
@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 工厂函数的新方法在许多情况下应该允许重用已编写的工厂函数,因为在通过 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
这将执行所有需要会话级“db”资源的测试两次,接收由工厂函数的两次相应调用创建的值。
使用 @fixture 装饰器时无需 pytest_funcarg__
前缀¶
使用 @fixture
装饰器时,函数名称表示资源可以作为函数参数被访问的名称
@pytest.fixture()
def db(request): ...
可以请求 funcarg 资源的名称是 db
。
你仍然可以使用“旧的”非装饰器方式来指定 funcarg 工厂函数,即
def pytest_funcarg__db(request): ...
但那样就无法定义作用域和参数化。因此建议使用工厂装饰器。
解决会话级设置 / 自动使用 fixture¶
pytest 长期以来提供了 pytest_configure 和 pytest_sessionstart 钩子函数,这些函数通常用于设置全局资源。这存在几个问题
在分布式测试中,管理进程会设置测试资源,但这些资源从未被需要,因为它只协调工作进程的测试运行活动。
如果你只执行收集(使用“–collect-only”),资源设置仍将被执行。
如果 pytest_sessionstart 包含在某个子目录的 conftest.py 文件中,它将不会被调用。这源于此钩子实际上用于报告的事实,特别是带有平台/自定义信息的测试头。
此外,除了实现 pytest_runtest_setup()
钩子并自行处理作用域/缓存外,很难从插件或 conftest 文件中定义作用域设置。而且,使用参数化几乎不可能做到这一点,因为 pytest_runtest_setup()
在测试执行期间被调用,而参数化发生在收集时。
由此可见,pytest_configure/session/runtest_setup 通常不适合实现常见的 fixture 需求。因此,pytest-2.3 引入了 自动使用 fixture(无需请求的 fixture),它们与通用 fixture 机制完全集成,并废弃了许多以前使用 pytest 钩子的方式。
funcarg/fixture 的发现现在在收集时发生¶
自 pytest-2.3 以来,fixture/funcarg 工厂函数的发现已在收集时进行处理。这对于大型测试套件来说效率更高。此外,将来调用“pytest –collect-only”应该能够显示大量的设置信息,从而提供一种很好的方法来概览项目中 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 为你处理好一切。如果你使用了参数化和利用
request.cached_setup()
的 funcarg 工厂函数,建议花几分钟简化你的 fixture 函数代码,转而使用 Fixtures reference 装饰器。这还将允许利用自动的按资源分组测试功能。