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,会在加载测试之前以及导入模块之前自动修补您的环境,并且该修补程序会在测试会话结束时自动撤消。