谁能告诉我为什么这行不通?
>>> import mock
>>> @mock.patch('datetime.date.today')
... def today(cls):
... return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)
也许有人能提出一个更好的办法?
谁能告诉我为什么这行不通?
>>> import mock
>>> @mock.patch('datetime.date.today')
... def today(cls):
... return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)
也许有人能提出一个更好的办法?
当前回答
值得注意的是,Mock文档专门讨论了datetime.date.today,并且可以在不创建虚拟类的情况下做到这一点:
https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking
>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
... mock_date.today.return_value = date(2010, 10, 8)
... mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
... assert mymodule.date.today() == date(2010, 10, 8)
... assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...
其他回答
http://blog.xelnor.net/python-mocking-datetime/中讨论了几种解决方案。总而言之:
模拟对象-简单有效,但打破isinstance()检查:
target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
patched.now.return_value = target
print(datetime.datetime.now())
模拟类
import datetime
import mock
real_datetime_class = datetime.datetime
def mock_datetime_now(target, dt):
class DatetimeSubclassMeta(type):
@classmethod
def __instancecheck__(mcs, obj):
return isinstance(obj, real_datetime_class)
class BaseMockedDatetime(real_datetime_class):
@classmethod
def now(cls, tz=None):
return target.replace(tzinfo=tz)
@classmethod
def utcnow(cls):
return target
# Python2 & Python3 compatible metaclass
MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})
return mock.patch.object(dt, 'datetime', MockedDatetime)
使用:
with mock_datetime_now(target, datetime):
....
下面是另一种模拟datetime.date.today()的方法,其额外的好处是其余的datetime函数继续工作,因为模拟对象被配置为包装原始的datetime模块:
from unittest import mock, TestCase
import foo_module
class FooTest(TestCase):
@mock.patch(f'{foo_module.__name__}.datetime', wraps=datetime)
def test_something(self, mock_datetime):
# mock only datetime.date.today()
mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
# other calls to datetime functions will be forwarded to original datetime
注意mock.patch()的wraps=datetime参数——当foo_module使用date.today()之外的其他datetime函数时,它们将被转发到原始包装的datetime模块。
我通过将datetime导入为realdatetime并将我在模拟中需要的方法替换为实际方法来完成这项工作:
import datetime as realdatetime
@mock.patch('datetime')
def test_method(self, mock_datetime):
mock_datetime.today = realdatetime.today
mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)
CPython实际上使用纯python Lib/datetime.py和c优化的Modules/ _datetimmodule .c实现了datetime模块。c优化版本不能打补丁,但纯python版本可以。
在Lib/datetime.py中纯python实现的底部是这样的代码:
try:
from _datetime import * # <-- Import from C-optimized module.
except ImportError:
pass
这段代码导入了所有c优化的定义,并有效地替换了所有纯python定义。我们可以通过以下方式强制CPython使用datetime模块的纯python实现:
import datetime
import importlib
import sys
sys.modules["_datetime"] = None
importlib.reload(datetime)
通过设置sys。modules["_datetime"] = None,我们告诉Python忽略c优化的模块。然后重新加载导致从_datetime导入失败的模块。现在纯python定义仍然存在,可以正常打补丁。
如果你正在使用Pytest,那么在conftest.py中包含上面的代码片段,你就可以正常地修补datetime对象了。
也许您可以使用自己的“today()”方法,在需要的地方打补丁。使用utcnow()的示例可以在这里找到:https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default