You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从根目录运行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 -rAf
    
    create_app()无法获取到正确的环境变量,测试失败。
  • 仅执行功能测试:
    /tests $ pytest functional_tests/test_functional -s -rAF
    
    环境变量正常传递,测试成功。

原因分析与解决方案

核心原因:模块导入时机差异

当执行全量测试时,单元测试(unit_tests/test_service.py)可能在功能测试的环境变量mock fixture生效前,就提前导入了src模块(比如直接导入Container或依赖它的服务类)。

Container类的顶层代码(打印环境变量、加载配置)会在模块加载时立即执行,此时环境变量还未被mock,拿到的是默认值(比如DEV);而单独运行功能测试时,所有模块导入都发生在fixture生效后,环境变量是正确的TEST值。

解决方案

  1. 延迟单元测试中的模块导入
    不在单元测试的模块顶层导入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
            # 后续测试逻辑
    
  2. 提升环境变量mock的作用域
    将mock_settings_env_vars fixture的作用域改为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。

  3. 重构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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 13:35:45