如何测试可以使用环境变量初始化的数据类?

Bio*_*eek 3 python environment-variables pytest python-3.x python-dataclasses

我有以下数据类:

import os
import dataclasses

@dataclasses.dataclass
class Example:
    host: str = os.environ.get('SERVICE_HOST', 'localhost')
    port: str = os.environ.get('SERVICE_PORT', 30650)
Run Code Online (Sandbox Code Playgroud)

我该如何为此编写测试?我尝试了以下看起来应该有效的方法:

from stackoverflow import Example
import os


def test_example(monkeypatch):
    # GIVEN environment variables are set for host and port
    monkeypatch.setenv('SERVICE_HOST', 'server.example.com')
    monkeypatch.setenv('SERVICE_PORT', '12345')
    #   AND a class instance is initialized without specifying a host or port
    example = Example()
    # THEN the instance should reflect the host and port specified in the environment variables
    assert example.host == 'server.example.com'
    assert example.port == '12345'
Run Code Online (Sandbox Code Playgroud)

但这失败了:

====================================================================== test session starts ======================================================================
platform linux -- Python 3.8.12, pytest-7.1.2, pluggy-1.0.0
rootdir: /home/biogeek/tmp
collected 1 item                                                                                                                                                

test_example.py F                                                                                                                                         [100%]

=========================================================================== FAILURES ============================================================================
_________________________________________________________________________ test_example __________________________________________________________________________

monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f39de559220>

    def test_example(monkeypatch):
        # GIVEN environment variables are set for host and port
        monkeypatch.setenv('SERVICE_HOST', 'server.example.com')
        monkeypatch.setenv('SERVICE_PORT', '12345')
        #   AND a class instance is initialized without specifying a host or port
        example = Example()
        # THEN the instance should reflect the host and port specified in the environment variables
>       assert example.host == 'server.example.com'
E       AssertionError: assert 'localhost' == 'server.example.com'
E         - server.example.com
E         + localhost

test_example.py:12: AssertionError
==================================================================== short test summary info ====================================================================
FAILED test_example.py::test_example - AssertionError: assert 'localhost' == 'server.example.com'
======================================================================= 1 failed in 0.05s =======================================================================
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 10

您的测试失败是因为您的代码在导入模块时加载了环境变量。模块级代码很难测试,因为os.environ.get()设置默认值的调用在测试运行之前就已经运行了。您必须有效地从模块缓存中删除模块sys.modules,并且仅在模拟os.environ环境变量以测试导入时发生的情况后才导入模块。

您可以改为使用dataclass.field()带有default_factory参数的 a; 每当您创建数据类的实例时,都会执行一个可调用函数来获取默认值:

import os
from dataclasses import dataclass, field
from functools import partial


@dataclass
class Example:
    host: str = field(default_factory=partial(os.environ.get, 'SERVICE_HOST', 'localhost'))
    port: str = field(default_factory=partial(os.environ.get, 'SERVICE_PORT', '30650'))
Run Code Online (Sandbox Code Playgroud)

我使用该functools.partial()对象创建了一个可调用的对象,它将使用os.environ.get()给定的名称和默认值进行调用。

请注意,我还将 的默认值更改为SERVICE_PORT字符串;毕竟,该port字段被注释为 a str,而不是int. :-)

如果您必须在导入时从环境变量中设置这些默认值,那么您可以在模块pytest中模拟这些环境变量;这些是在导入测试之前导入的,因此您有机会在导入被测模块之前进行调整。但是,这不会让您使用不同的默认值运行多个测试:conftest.py

# add to conftest.py at the same package level, or higher.

@pytest.fixture(autouse=True, scope="session")
def mock_environment(monkeypatch):
    monkeypatch.setenv('SERVICE_HOST', 'server.example.com')
    monkeypatch.setenv('SERVICE_PORT', '12345')
Run Code Online (Sandbox Code Playgroud)

上面的固定装置示例,当放置在 中时conftest.py,会在加载测试之前以及导入模块之前自动修补您的环境,并且该修补程序会在测试会话结束时自动撤消。