Python:试图模拟datetime.date.today()但不工作
谁能告诉我为什么这不起作用?
>>> 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.patch
的方式不太正确。 当作为装饰器使用时, 它只在装饰函数中用Mock
对象replace给定的函数/类(在本例中为datetime.date.today
)。 所以,只有在你today()
将datetime.date.today
是一个不同的function,这似乎并不是你想要的。
你真正想要的东西似乎更像这样:
@mock.patch('datetime.date.today') def test(): datetime.date.today.return_value = date(2010, 1, 1) print datetime.date.today()
不幸的是,这是行不通的。
>>> test() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "build/bdist.macosx-10.6-universal/egg/mock.py", line 557, in patched File "build/bdist.macosx-10.6-universal/egg/mock.py", line 620, in __enter__ TypeError: can't set attributes of built-in/extension type 'datetime.date'
由于Python内置types是不可变的,因此失败 – 请参阅此答案以获取更多详细信息。
在这种情况下,我将自己的子类datetime.date并创build正确的function:
import datetime class NewDate(datetime.date): @classmethod def today(cls): return cls(2010, 1, 1) datetime.date = NewDate
现在你可以这样做:
>>> datetime.date.today() NewDate(2010, 1, 1)
另一种select是使用https://github.com/spulec/freezegun/
安装它:
pip install freezegun
并使用它:
from freezegun import freeze_time @freeze_time("2012-01-01") def test_something(): from datetime import datetime print(datetime.now()) # 2012-01-01 00:00:00 from datetime import date print(date.today()) # 2012-01-01
它也会影响来自其他模块的方法调用中的其他date时间调用:
other_module.py:
from datetime import datetime def other_method(): print(datetime.now())
main.py:
from freezegun import freeze_time @freeze_time("2012-01-01") def test_something(): import other_module other_module.other_method()
最后:
$ python main.py # 2012-01-01
对于什么是值得的,模拟文档专门讨论datetime.date.today,而且可以做到这一点,而不必创build一个虚拟类:
http://www.voidspace.org.uk/python/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) ...
要添加到丹尼尔G的解决scheme:
from datetime import date class FakeDate(date): "A manipulable date replacement" def __new__(cls, *args, **kwargs): return date.__new__(date, *args, **kwargs)
这创build了一个类,在实例化时,它将返回一个正常的datetime.date对象,但它也是可以改变的。
@mock.patch('datetime.date', FakeDate) def test(): from datetime import date FakeDate.today = classmethod(lambda cls: date(2010, 1, 1)) return date.today() test() # datetime.date(2010, 1, 1)
我想我迟了一点,但我认为这里的主要问题是,你直接修补datetime.date.today,根据文档,这是错误的。
例如,您应该将导入的引用修补到testing函数所在的文件中。
比方说,你有一个functions.py文件,你有以下几点:
import datetime def get_today(): return datetime.date.today()
那么,在你的testing中,你应该有这样的东西
import datetime import unittest from functions import get_today from mock import patch, Mock class GetTodayTest(unittest.TestCase): @patch('functions.datetime') def test_get_today(self, datetime_mock): datetime_mock.date.today = Mock(return_value=datetime.strptime('Jun 1 2005', '%b %d %Y')) value = get_today() # then assert your thing...
希望这有助于一点点。
基于Daniel G解决scheme,您可以使用以下方法。 这个优点是不会用isinstance(d, datetime.date)
打破types检查。
import mock def fixed_today(today): from datetime import date class FakeDateType(type): def __instancecheck__(self, instance): return isinstance(instance, date) class FakeDate(date): __metaclass__ = FakeDateType def __new__(cls, *args, **kwargs): return date.__new__(date, *args, **kwargs) @staticmethod def today(): return today return mock.patch("datetime.date", FakeDate)
基本上,我们用我们自己的python子类replace了基于C的datetime.date
类,它产生了原始的datetime.date
实例,并且完全按照本地的datetime.date
来响应isinstance()
查询。
在testing中使用它作为上下文pipe理器:
with fixed_today(datetime.date(2013, 11, 22)): # run the code under test # note, that these type checks will not break when patch is active: assert isinstance(datetime.date.today(), datetime.date)
类似的方法可以用来模拟datetime.datetime.now()
函数。
一般来说,你可能会在某个地方将datetime
或者datetime.date
导入到模块中。 嘲讽方法的一个更有效的方法是将其打包到导入它的模块上。 例:
a.py
from datetime import date def my_method(): return date.today()
那么对于你的testing,模拟对象本身将作为parameter passing给testing方法。 你可以用你想要的结果值设置模拟,然后调用你的方法。 然后你会断言你的方法做了你想要的。
>>> import mock >>> import a >>> @mock.patch('a.date') ... def test_my_method(date_mock): ... date_mock.today.return_value = mock.sentinel.today ... result = a.my_method() ... print result ... date_mock.today.assert_called_once_with() ... assert mock.sentinel.today == result ... >>> test_my_method() sentinel.today
一个警告的话。 肯定有可能会嘲笑。 当你这样做的时候,它会让你的testing变得更长,更难理解,而且不可能维护。 在嘲笑一个像datetime.date.today
这样简单的方法之前,问问自己是否真的需要嘲笑它。 如果你的testing很简短,并且没有嘲讽function,那么你可能只是在看你正在testing的代码的内部细节,而不是你需要模拟的对象。
也许你可以使用你自己的“今天()”方法,你将在需要时进行修补。 模拟utcnow()的例子可以在这里find: https ://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default
我使用自定义装饰器实现了@ user3016183方法:
def changeNow(func, newNow = datetime(2015, 11, 23, 12, 00, 00)): """decorator used to change datetime.datetime.now() in the tested function.""" def retfunc(self): with mock.patch('mymodule.datetime') as mock_date: mock_date.now.return_value = newNow mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw) func(self) return retfunc
我想这可能会帮助别人
http://blog.xelnor.net/python-mocking-datetime/中讨论了几种解决scheme。; 综上所述:
模拟对象 – 简单而高效,但打破了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): ....