如何在conftest.py中使用pytest对环境进行monkeypatch?

问题描述 投票:0回答:3

我的主文件中有一个全局对象

# reporter.py

from os import environ
from influxdb import InfluxDBClient

influxdb_client = InfluxDBClient(host=environ['INFLUXCLOUD_HOST'],
                                 username=environ['INFLUXCLOUD_USERNAME'],
                                 password=environ['INFLUXCLOUD_PASSWORD'],
                                 ssl=True,
                                 timeout=4*60)

def foo():
    pass

我正在使用 pytest,我想为这些环境变量设置虚假值。我的 conftest.py 中有以下内容:

# conftest.py

import pytest

@pytest.fixture(scope='session', autouse=True)
def setup_env(monkeypatch):
    monkeypatch.setenv('INFLUXCLOUD_HOST', 'host')
    monkeypatch.setenv('INFLUXCLOUD_USERNAME', 'username')
    monkeypatch.setenv('INFLUXCLOUD_PASSWORD', 'password')

但是,当我在测试文件中

import reporter
时,我得到一个
KeyError
,表明环境中缺少 INFLUXCLOUD_HOST。

为什么 pytest 不执行

setup_env
并猴子修补我的环境?有办法吗?

python pytest
3个回答
12
投票

这里的问题在于误解了会话范围的固定装置是什么。

要知道哪些测试和自动使用的装置确实存在,pytest 需要导入测试文件和 conftest 插件。然后它扫描导入的模块,并查找固定装置、测试函数、测试类等。这在 pytest 术语中称为“集合”。

只有在收集了所有测试后,pytest 才会决定执行它们,并安排执行计划,特别是在准备好固定装置时。在任何测试开始之前以及所有测试完成之后,首先准备会话范围的固定装置,最后拆除。

但是,测试文件和conftest的导入假定这些模块的执行——就像导入任何其他Python模块一样,与pytest无关。

因此,当您从测试文件中执行

import reporter
时,或者即使您将该全局变量直接放入测试文件中,该模块也会被执行,并尝试使用环境变量。但是这些装置尚未执行(并且 pytest 还不知道它们的存在)。因此,它失败了。

即使您从测试函数内部

import reporter
,这也没有多大帮助,因为 pytest 可能会在收集阶段之前尝试导入该
reporter.py
模块。由于缺少测试函数/类,Pytest 会将其过滤掉,但导入尝试将完成并失败。

这里最好的解决方案是将客户端“打包”到一个固定装置中,并使用该固定装置而不是全局变量。


11
投票

pytest 6.2 开始,您可以直接使用

MonkeyPatch
对象代替
monkeypatch
固定装置,作为实例或上下文管理器。

(谢尔盖已经提供了关于“为什么”问题的坚实背景;这试图解决“如何”。)

上下文管理器(推荐):

因为与

monkeypatch
夹具不同,直接创建的实例不会自动
undo()
编辑。

# test_reporter.py

from pytest import MonkeyPatch


def test_get_client_username():
    with MonkeyPatch.context() as mp:
        mp.setenv('INFLUXCLOUD_HOST', 'host')
        mp.setenv('INFLUXCLOUD_USERNAME', 'username')
        mp.setenv('INFLUXCLOUD_PASSWORD', 'password')

        from src.reporter import influxdb_client

        assert influxdb_client._username == 'username'

直接使用实例:

# conftest.py

from pytest import MonkeyPatch


mp = pytest.MonkeyPatch()
mp.setenv('INFLUXCLOUD_HOST', 'host')
mp.setenv('INFLUXCLOUD_USERNAME', 'username')
mp.setenv('INFLUXCLOUD_PASSWORD', 'password')

假设的文件结构供参考:

src/
    reporter.py
    __init__.py
test/
    conftest.py
    test_reporter.py

0
投票

作为SergeyNathaniel提供的出色答案的补充,我遵循了Nathaniel的方法,但我的环境变量仍然没有正确设置。

就我而言,我有以下设置:大量测试,我使用

monkeypatch.setattr
来模拟要测试的模块中导入的包的属性和方法。因此,我需要模块的顶级导入,以便
monkeypatch
可以访问该范围来修补它们导入的属性和方法(我相信这与 https://docs.python.org 没有太大不同) /3/library/unittest.mock.html#where-to-patch,但其他人可能会纠正我)。然后我导入我想要测试的功能。

在这个设置中,并使用纳撒尼尔的建议(这没有错,我的设置不同),我有类似的东西:

# test.py
from package_to_test import module_to_test
from package_to_test.module_to_test import (
   function_to_test1
   function_to_test2
   ...
   
)

...
# other tests
...

def test_function_using_env_var(monkeypatch):
   
    with monkeypatch.context() as monkeycontext:
        monkeycontext.setenv("ENV_VAR_TO_PATCH", "mocked_env_var")

        from package_to_test.module_to_test import function_using_env_var

        results = function_using_env_var()
        assert results == ...

在这种情况下,我仍然没有正确设置环境变量。 现在可能还有其他解决方案。我尝试将测试分成另一个测试模块,但这似乎没有做到。

我解决这个问题的方法是通过

importlib.reload
:

重新加载模块
# test.py
from package_to_test import module_to_test
from package_to_test.module_to_test import (
   function_to_test1
   function_to_test2
   ...
   
)
import importlib

...
# other tests
...

def test_function_using_env_var(monkeypatch):
   
    with monkeypatch.context() as monkeycontext:
        monkeycontext.setenv("ENV_VAR_TO_PATCH", "mocked_env_var")
        
        importlib.reload(module_to_test)
        from package_to_test.module_to_test import function_using_env_var

        results = function_using_env_var()
        assert results == ...

可能有更简单的方法来解决这个问题,所以如果有人知道,请告诉!

© www.soinside.com 2019 - 2024. All rights reserved.