pytest fixture中yield from其他fixture的含义及报错、scope冲突解决
原代码中yield from test_mode_on()的作用
test_mode_on是典型的生成器风格pytest fixture,包含三部分逻辑:
- yield前的setup逻辑:修改环境变量开启autologging测试模式,保存修改前的环境变量值
- yield语句:让出执行权给调用该fixture的测试用例/上层fixture
- finally块的teardown逻辑:测试执行结束后还原环境变量
原代码写yield from test_mode_on()的核心目的是复用test_mode_on的全部setup、teardown逻辑,让session级的自动生效fixtureenable_test_mode_by_default_for_autologging_integrations可以直接继承测试模式的开关逻辑,不需要重复写代码,实现整个测试会话全局自动开启测试模式的效果。
这种写法在pytest 7.0之前的版本可以正常运行,因为旧版本pytest没有限制直接调用fixture函数。pytest 7.0及以上版本正式将直接调用fixture的行为从警告调整为报错,就是你现在遇到的错误提示。
问题修复方案
你尝试的参数注入方式报错是因为test_mode_on默认是function级scope,和session级的enable_test_mode_by_default_for_autologging_integrations scope不兼容,pytest不允许高scope的fixture依赖低scope的fixture。推荐以下两种修复方案:
方案1:抽离公共逻辑(最优,兼容原有全部逻辑)
把测试模式的上下文逻辑抽成普通生成器函数,两个fixture分别调用这个公共函数,完全规避fixture直接调用和scope冲突问题:
# 抽离的公共普通函数,不是fixture def _test_mode_on_context(): try: prev_env_var_value = os.environ.pop(_AUTOLOGGING_TEST_MODE_ENV_VAR, None) os.environ[_AUTOLOGGING_TEST_MODE_ENV_VAR] = "true" assert is_testing() yield finally: if prev_env_var_value: os.environ[_AUTOLOGGING_TEST_MODE_ENV_VAR] = prev_env_var_value else: del os.environ[_AUTOLOGGING_TEST_MODE_ENV_VAR] # 原有function级fixture,保持原有调用逻辑不变 @pytest.fixture def test_mode_on(): yield from _test_mode_on_context() # session级自动fixture,调用普通公共函数,无scope冲突 @pytest.fixture(autouse=True, scope="session") def enable_test_mode_by_default_for_autologging_integrations(): yield from _test_mode_on_context()
这个方案完全保留原有所有功能:test_mode_on还是function级,供单独标记的测试用例使用;session级fixture全局生效,两者互不干扰。
方案2:统一fixture scope(适合无单测修改环境变量的场景)
如果你的测试场景中没有单测会修改_AUTOLOGGING_TEST_MODE_ENV_VAR这个环境变量,不需要每个单测重置环境,可以直接把test_mode_on的scope调整为session级,通过依赖注入使用:
@pytest.fixture(scope="session") def test_mode_on(): try: prev_env_var_value = os.environ.pop(_AUTOLOGGING_TEST_MODE_ENV_VAR, None) os.environ[_AUTOLOGGING_TEST_MODE_ENV_VAR] = "true" assert is_testing() yield finally: if prev_env_var_value: os.environ[_AUTOLOGGING_TEST_MODE_ENV_VAR] = prev_env_var_value else: del os.environ[_AUTOLOGGING_TEST_MODE_ENV_VAR] @pytest.fixture(autouse=True, scope="session") def enable_test_mode_by_default_for_autologging_integrations(test_mode_on): # 依赖注入后pytest会自动执行test_mode_on的逻辑,无需额外代码 yield
内容的提问来源于stack exchange,提问作者Ark-kun

