如何模拟未按名称调用的函数?
How can I mock a function that isn't called by name?
假设我有一个装饰器,它收集它装饰的所有函数,以便在将来某个时候调用。
mydecorator.py
class CallLater(object):
funcs = []
def __init__(self, func):
self.funcs.append(func)
@classmethod
def call_now(cls, *args, **kwargs):
for func in cls.funcs:
func(*args, **kwargs)
然后,我在一个模块中有一个函数,其中一个函数将被我的装饰器保存。
mymodule.py
import logging
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
@CallLater
def log_a():
logging.info("A")
@CallLater
def log_b():
logging.info("B")
def log_c():
logging.info("C")
现在,如果我导入 mymodule
并调用 CallLater.call_now()
,将调用 log_a
和 log_b
。但是假设在测试期间,我希望 log_b
替换为 log_c
。我会尝试用模拟进行替换。
mock_test.py
import logging
import pytest
from mymodule import log_a, log_c
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
pytest_plugins = ('pytest_mock',)
def test_mocking(mocker, caplog):
mocker.patch('mymodule.log_b', log_c)
CallLater.call_now()
logs = [rec.message for rec in caplog.records]
assert logs == ["A", "C"]
但是当我 运行 pytest
时,我发现我的模拟没有工作。
FAILED mock_test.py::test_mocking - AssertionError: assert ['A', 'B'] == ['A', 'C']
我想 'mymodule.log_b'
是错误的模拟目标,因为它没有被调用为 mymodule.log_b()
,但我不确定在这种情况下应该使用什么。任何建议表示赞赏!
这是不可能的,问题是函数已经在加载时分配给了列表。我能看到的唯一修补方法是直接修补 CallLater.funcs
,这有点尴尬,因为你必须手动将 log_b
替换为 log_c
- 但它是这样的:
import logging
from unittest import mock
from mymodule import log_c
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
def test_mocking(caplog):
funcs = [log_c if f.__name__ == 'log_b' else f for f in CallLater.funcs]
with mock.patch.object(CallLater, 'funcs', funcs):
CallLater.call_now()
logs = [rec.message for rec in caplog.records]
assert logs == ["A", "C"]
请注意,您不能直接与函数(例如f == log_b
)进行比较,因为log_b
是装饰函数,而不是保存在CallLater.funcs
中的函数。
假设我有一个装饰器,它收集它装饰的所有函数,以便在将来某个时候调用。
mydecorator.py
class CallLater(object):
funcs = []
def __init__(self, func):
self.funcs.append(func)
@classmethod
def call_now(cls, *args, **kwargs):
for func in cls.funcs:
func(*args, **kwargs)
然后,我在一个模块中有一个函数,其中一个函数将被我的装饰器保存。
mymodule.py
import logging
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
@CallLater
def log_a():
logging.info("A")
@CallLater
def log_b():
logging.info("B")
def log_c():
logging.info("C")
现在,如果我导入 mymodule
并调用 CallLater.call_now()
,将调用 log_a
和 log_b
。但是假设在测试期间,我希望 log_b
替换为 log_c
。我会尝试用模拟进行替换。
mock_test.py
import logging
import pytest
from mymodule import log_a, log_c
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
pytest_plugins = ('pytest_mock',)
def test_mocking(mocker, caplog):
mocker.patch('mymodule.log_b', log_c)
CallLater.call_now()
logs = [rec.message for rec in caplog.records]
assert logs == ["A", "C"]
但是当我 运行 pytest
时,我发现我的模拟没有工作。
FAILED mock_test.py::test_mocking - AssertionError: assert ['A', 'B'] == ['A', 'C']
我想 'mymodule.log_b'
是错误的模拟目标,因为它没有被调用为 mymodule.log_b()
,但我不确定在这种情况下应该使用什么。任何建议表示赞赏!
这是不可能的,问题是函数已经在加载时分配给了列表。我能看到的唯一修补方法是直接修补 CallLater.funcs
,这有点尴尬,因为你必须手动将 log_b
替换为 log_c
- 但它是这样的:
import logging
from unittest import mock
from mymodule import log_c
from mydecorator import CallLater
logging.basicConfig(level=logging.INFO)
def test_mocking(caplog):
funcs = [log_c if f.__name__ == 'log_b' else f for f in CallLater.funcs]
with mock.patch.object(CallLater, 'funcs', funcs):
CallLater.call_now()
logs = [rec.message for rec in caplog.records]
assert logs == ["A", "C"]
请注意,您不能直接与函数(例如f == log_b
)进行比较,因为log_b
是装饰函数,而不是保存在CallLater.funcs
中的函数。