在 Pytest 中测试 class 的方法时如何填充包变量?

How do you populate package variables when testing a class's method in Pytest?

我正在为 class 编写一个测试套件,它需要我定义的 util 包中的变量:

# util/__init__.py

codes = None

def load_codes():
    """Populates util.codes with the contents of codes.yml"""
    with open('codes.yml', 'r') as f:
        global codes
        codes = yaml.safe_load(f)

正在测试的 class Filter 是一个更大应用程序的插件;通常 util.load_codes() 在应用程序启动时调用,因此当 Filter 需要读取 util.codes 时,它保证被填充。过滤器不应该关心将文件加载到包 var.

对于测试本身,我正在加载 class 实例作为夹具:

# tests/filter.py
import pytest
from unittest.mock import AsyncMock
from plugins.filter import Filter

@pytest.fixture
def filterplugin():
    bot = AsyncMock()
    return Filter(bot=bot)


@pytest.mark.asyncio
class TestFilterLogic:
    async def test_basic(self, filterplugin):
        msg = AsyncMock()
        output = await filterplugin.filter_handler(msg)
        assert output == True

过滤器本身又大又复杂,但演示问题的 MVP 是:

# plugins/filter.py
from util import codes

class Filter():
    async def filter_handler(self):
        print(codes.keys())
        return True

filter_handlerutil.codes 上调用 .keys() 时,此测试失败并返回 AttributeError;这是 None.

我试图通过在夹具和测试本身中调用导入 util 和调用 util.load_codes() 来解决这个问题,但没有效果。测试仍然失败,表明 util 中的 codes var 是 None.

如何正确填充此包变量,以便我的 class 在独立于较大应用程序的其余部分进行单元测试时可以读取它?

when Filter needs to read util.codes

显示的实现未读取 util.codes。它在导入时读取对 util.codes 值的本地引用,即对 None 的本地引用。 load_codes().

不会修改该局部变量

有几种方法可以解决这个问题。

  1. codes 初始化为字典并在 load_codes() 中修改该实例(不重新分配)。
# util/__init__.py

# codes = None  # -
codes = {}      # +

def load_codes():
    """Populates util.codes with the contents of codes.yml"""
    with open('codes.yml', 'r') as f:
        global codes
        # codes = yaml.safe_load(f)      # -
        codes.clear()                    # +
        codes.update(yaml.safe_load(f))  # +
  1. 导入模块并将模块变量引用为模块的属性。
# plugins/filter.py

# from util import codes  # -
import util               # +

class Filter():
    async def filter_handler(self):
        # print(codes.keys())     # -
        print(util.codes.keys())  # +
        return True
  1. 懒惰地将模块变量作为局部变量导入。
# plugins/filter.py

# from util import codes        # -

class Filter():
    async def filter_handler(self):
        from util import codes  # +
        print(codes.keys())
        return True
  1. 延迟导入 Filter,假设 load_codes() 在 fixture 之前被调用。
# tests/filter.py

import pytest
from unittest.mock import AsyncMock
# from plugins.filter import Filter    # -

@pytest.fixture
def filterplugin():
    from plugins.filter import Filter  # +
    bot = AsyncMock()
    return Filter(bot=bot)