在 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_handler
在 util.codes
上调用 .keys()
时,此测试失败并返回 AttributeError
;这是 None.
我试图通过在夹具和测试本身中调用导入 util
和调用 util.load_codes()
来解决这个问题,但没有效果。测试仍然失败,表明 util
中的 codes
var 是 None.
如何正确填充此包变量,以便我的 class 在独立于较大应用程序的其余部分进行单元测试时可以读取它?
when
Filter
needs to readutil.codes
显示的实现未读取 util.codes
。它在导入时读取对 util.codes
值的本地引用,即对 None
的本地引用。 load_codes()
.
有几种方法可以解决这个问题。
- 将
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)) # +
- 导入模块并将模块变量引用为模块的属性。
# 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
- 懒惰地将模块变量作为局部变量导入。
# plugins/filter.py
# from util import codes # -
class Filter():
async def filter_handler(self):
from util import codes # +
print(codes.keys())
return True
- 延迟导入
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)