如何在导入所有内容之前运行 pytest conftest

how to run pytest conftest before everything is imported

提问人:Amin Ba 提问时间:3/10/2022 最后编辑:Amin Ba 更新时间:11/13/2023 访问量:1311

问:

这是我问题的简化版本。

我有一个 python 应用程序,其结构如下:

my_project
    my_app
        __init__.py
        settings.py
    tests
        __init__.py
        conftest.py
        my_test.py
     venv
# settings.py

from dotenv import load_dotenv
load_dotenv() 
MY_VALUE = os.environ["MY_KEY"]

出于某些原因,我不想在这里添加 .env 文件。我也不想在os.environ上使用get方法

我想运行此测试

# my_test.py

from my_app import settings # I need this for some reasons

def test_something():
    assert True

def test_env():
    assert os.environ["MY_KEY"] == 'MY_VALUE'

但是,我得到一个,因为一旦我导入,该行就会运行,并且因为不在环境中,我得到.KeyErrorsettings.pyMY_VALUE = os.environ["MY_KEY"]MY_KEYKeyError

我的想法是,我可以像下面这样解释

import os
from unittest import mock

import pytest


@pytest.fixture(autouse=True)
def set_env(config):
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE'}):
        yield

但这仍然是同样的问题。

我怎样才能先执行这个 conftest。

还要注意,如果我注释掉,我的两个测试都会通过from my_app import settings

注意:我不想使用的原因是我想在应用程序或测试初始化之前强制 env 变量存在。这样做的原因是,实际上,当我的应用程序在 docker 容器中运行时,env 变量先于其他所有变量存在,我想增加 dev-prod 奇偶校验。https://12factor.net/dev-prod-parityos.environ.get("MY_KEY")

python django pytest

评论

0赞 Devang Sanghani 3/10/2022
为什么不把灯具放在 settings.py 本身呢?
1赞 Amin Ba 3/10/2022
test 应与 applocation 分开。这完全没有意义
0赞 AKX 3/10/2022
github.com/MobileDynasty/pytest-env 怎么样?
0赞 Abdul Aziz Barkat 3/10/2022
这回答了你的问题吗?如何将环境变量传递给 pytest
0赞 gold_cy 3/10/2022
它是一个全局变量,在任何修补之前加载,您可以在修补后重新加载以获得适当的值

答:

0赞 Vadim Denisov 11/13/2023 #1

发生这种情况的原因是 pytest 首先收集所有测试,然后运行它们。

在收集阶段,pytest 导入 和 .conftest.pymy_test.py

在测试运行阶段,pytest 会应用夹具并自行运行测试函数。

在您的情况下,在收集阶段,imports ,导致读取环境变量的副作用。由于固定装置尚未应用,因此提出了 a。my_test.pymy_app/settings.pyos.environ["MY_KEY"]KeyError

有两种方法可以解决此问题:

1. 构建没有副作用的代码。

无需初始化模块内部的设置,而是可以将初始化移动到函数中。此外,还可用于确保仅加载一次配置。get_settingsfunctools.cache

# my_app/settings.py
import functools
from dotenv import load_dotenv

@functools.cache
def get_settings():
    load_dotenv() 
    MY_VALUE = os.environ["MY_KEY"]
    return {"MY_KEY": MY_VALUE}


# tests/conftest.py
import os
from unittest import mock
import pytest

@pytest.fixture(autouse=True)
def set_env():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE'}):
        yield

# tests/my_test.py
from my_app import get_setting


def test_env():
    settings = get_setting()
    assert settings["MY_KEY"] == 'MY_VALUE'

2. 延迟导入settings.py

如果第一点不适用,可以在测试中导入。这样,它将在应用夹具后的试运行阶段导入。settings.py

# my_app/settings.py
from dotenv import load_dotenv
load_dotenv() 
MY_VALUE = os.environ["MY_KEY"]


# tests/conftest.py
import os
from unittest import mock
import pytest

@pytest.fixture(autouse=True)
def set_env():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE'}):
        yield

# tests/my_test.py
def test_env():
    from my_app import settings
    
    assert settings.MY_KEY == 'MY_VALUE'

请注意,Python 将在首次导入后缓存设置。如果要使用不同的设置运行多个测试,可以:

# tests/conftest.py
import os
import importlib
from unittest import mock
import pytest

@pytest.fixture
def set_env_1():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE_1'}):
        yield

@pytest.fixture
def set_env_2():
    with mock.patch.dict(os.environ, {'MY_KEY': 'MY_VALUE_2'}):
        yield

@pytest.fixture
def settings():
    from py_helpers import settings
    return importlib.reload(settings)


# tests/my_test.py
def test_env_my_key_1(set_env_1, settings):
    assert settings.MY_KEY == 'MY_VALUE_1'

def test_env_my_key_2(set_env_2, settings):
    assert settings.MY_KEY == 'MY_VALUE_2'