从根目录运行pytest时功能测试无法获取环境变量的问题
问题:pytest全量测试与单独功能测试时环境变量传递不一致
应用代码结构
container.py
# container.py class Container(containers.DeclarativeContainer): wiring_config = containers.WiringConfiguration(modules=[".routes"]) print(f"$$$$$$$\n testinggggg - {os.environ.get('ENV')}\n$$$$$$") config = providers.Configuration( yaml_files=[ "./src/conf/common.yaml", # load the common config first config_file_path[ os.environ.get("ENV", "DEV") ], # env specific config - will override values in common.yaml ] ) config.load(envs_required=True) print(f"$$$$$$$\nconfig loaded as - {config.get('app')}\n$$$$$$") db_repository = providers.Resource(DBRepository, config.get("app.db_repository"))
init.py
# __init__.py def create_app(): print("@@@@@@ in create_app() @@@@@@@") container = Container() container.init_resources() app = Flask(__name__) app.container = container # connect url rules and register error handlers routes.configure(app) return app
测试目录结构
tests |--unit_tests |-- test_service.py |--functional_tests |-- test_functional.py
功能测试代码(test_functional.py)
import json import os from unittest import mock import pytest import requests from flask import url_for from test.util.mock_db import MockDB """ These tests spin up a testing instance of the app and test the APIs end to end """ @pytest.fixture(scope='module', autouse=True) def mock_settings_env_vars(): with mock.patch.dict( os.environ, { "ENV": "TEST", "DB_USERNAME": os.environ.get("DB_USERNAME", "root"), "DB_PASSWORD": os.environ.get("DB_PASSWORD", "admin"), "DB_HOST": os.environ.get("DB_HOST", "localhost"), "DB_PORT": os.environ.get("DB_PORT", "3306"), }, ): yield @pytest.fixture(scope='session', autouse=True) def app(): # test if pytest fixture for env vars works as expected assert os.environ["ENV"] == "TEST", f"ENV is set to {os.environ['ENV']}" print(f"********* HIIIII ********* {os.environ['ENV']}") from src import create_app mock_db = MockDB( os.environ["DB_USERNAME"], os.environ["DB_PASSWORD"], os.environ["DB_HOST"], os.environ["DB_PORT"], ) mock_db.setup() app = create_app() yield app mock_db.teardown() app.container.unwire() def test_check(client, app): response = client.get(url_for("check")) assert response.status_code == 200 assert response.data == b"You hit /"
问题现象
- 执行全量测试(在
/tests目录下):/tests $ pytest -s -rAfcreate_app()无法获取到正确的环境变量,测试失败。 - 仅执行功能测试:
环境变量正常传递,测试成功。/tests $ pytest functional_tests/test_functional -s -rAF
原因分析与解决方案
核心原因:模块导入时机差异
当执行全量测试时,单元测试(unit_tests/test_service.py)可能在功能测试的环境变量mock fixture生效前,就提前导入了src模块(比如直接导入Container或依赖它的服务类)。
Container类的顶层代码(打印环境变量、加载配置)会在模块加载时立即执行,此时环境变量还未被mock,拿到的是默认值(比如DEV);而单独运行功能测试时,所有模块导入都发生在fixture生效后,环境变量是正确的TEST值。
解决方案
延迟单元测试中的模块导入
不在单元测试的模块顶层导入src相关类,而是在测试函数内部或专属fixture中延迟导入,确保环境变量mock生效后再加载模块:# unit_tests/test_service.py示例 def test_service_logic(): with mock.patch.dict(os.environ, {"ENV": "TEST"}): from src.container import Container # 后续测试逻辑提升环境变量mock的作用域
将mock_settings_env_varsfixture的作用域改为session,确保所有测试模块加载前,环境变量就已被mock:@pytest.fixture(scope='session', autouse=True) def mock_settings_env_vars(): with mock.patch.dict( os.environ, { "ENV": "TEST", "DB_USERNAME": os.environ.get("DB_USERNAME", "root"), "DB_PASSWORD": os.environ.get("DB_PASSWORD", "admin"), "DB_HOST": os.environ.get("DB_HOST", "localhost"), "DB_PORT": os.environ.get("DB_PORT", "3306"), }, ): yield若单元测试不需要这些环境变量,可取消
autouse,为功能测试单独指定该fixture。重构Container类,避免顶层执行依赖环境变量的代码
将依赖环境变量的逻辑移到类方法中,而非模块加载时的顶层代码,确保环境变量设置后再执行:class Container(containers.DeclarativeContainer): wiring_config = containers.WiringConfiguration(modules=[".routes"]) @classmethod def init_config(cls): cls.config = providers.Configuration( yaml_files=[ "./src/conf/common.yaml", config_file_path[os.environ.get("ENV", "DEV")], ] ) cls.config.load(envs_required=True) cls.db_repository = providers.Resource(DBRepository, cls.config.get("app.db_repository"))然后在
create_app()中先调用Container.init_config(),再初始化资源。
内容的提问来源于stack exchange,提问作者Saturnian
相关产品推荐
相关产品推荐

