Trying to mock datetime.date.today(), but not working
有人能告诉我为什么这个不起作用吗?
1 2 3 4 5 6 7 8 | >>> 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) |
也许有人能提出一个更好的方法?
另一个选择是使用https://github.com/spulec/freezegun/
安装:
1 | pip install freezegun |
并使用它:
1 2 3 4 5 6 7 8 9 10 | 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 |
它还影响来自其他模块的方法调用中的其他日期时间调用:
其他模块:
1 2 3 4 | from datetime import datetime def other_method(): print(datetime.now()) |
MY.PY:
1 2 3 4 5 6 7 | from freezegun import freeze_time @freeze_time("2012-01-01") def test_something(): import other_module other_module.other_method() |
最后:
1 2 | $ python main.py # 2012-01-01 |
有一些问题。
首先,你使用
你真正想要的似乎更像这样:
1 2 3 4 | @mock.patch('datetime.date.today') def test(): datetime.date.today.return_value = date(2010, 1, 1) print datetime.date.today() |
不幸的是,这行不通:
1 2 3 4 5 6 | >>> 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内置类型是不可变的-有关详细信息,请参阅此答案。
在这种情况下,我将自己子类化datetime.date并创建正确的函数:
1 2 3 4 5 6 | import datetime class NewDate(datetime.date): @classmethod def today(cls): return cls(2010, 1, 1) datetime.date = NewDate |
现在你可以做:
1 2 | >>> datetime.date.today() NewDate(2010, 1, 1) |
为了实现它的价值,模拟文档专门讨论了datetime.date.today,并且可以在不创建虚拟类的情况下进行此操作:
http://www.voidspace.org.uk/python/mock/examples.html部分模拟
1 2 3 4 5 6 7 8 | >>> 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) ... |
我想我来的有点晚了,但我认为这里的主要问题是您正在直接修补datetime.date.today,根据文档,这是错误的。
例如,您应该修补导入到测试函数所在文件中的引用。
假设您有一个functions.py文件,其中包含以下内容:
1 2 3 4 | import datetime def get_today(): return datetime.date.today() |
那么,在你的测试中,你应该有这样的东西
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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的解决方案中:
1 2 3 4 5 6 | from datetime import date class FakeDate(date): "A manipulable date replacement" def __new__(cls, *args, **kwargs): return date.__new__(date, *args, **kwargs) |
这将创建一个类,该类在实例化时将返回一个普通的datetime.date对象,但也可以更改该对象。
1 2 3 4 5 6 7 | @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) |
几天前我也遇到了同样的情况,我的解决方案是在模块中定义一个函数来测试并模拟它:
1 2 | def get_date_now(): return datetime.datetime.now() |
今天我发现了Freezegun,它似乎很好地覆盖了这个案子。
1 2 3 4 5 6 7 8 | from freezegun import freeze_time import datetime import unittest @freeze_time("2012-01-14") def test(): assert datetime.datetime.now() == datetime.datetime(2012, 1, 14) |
基于Daniel G解决方案,您可以使用以下方法。本机具有不与
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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子类替换基于C的
在测试中用作上下文管理器:
1 2 3 4 | 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) |
类似的方法可以用来模拟
对我来说最简单的方法是:
1 2 3 4 5 6 | from unittest import patch, Mock def test(): datetime_mock = Mock(wraps=datetime) datetime_mock.now = Mock(return_value=datetime(1999, 1, 1) patch('target_module.datetime', new=datetime_mock).start() |
此解决方案的注意事项:来自
一般来说,您可以将
A.Py
1 2 3 4 | from datetime import date def my_method(): return date.today() |
然后对于您的测试,模拟对象本身将作为参数传递给测试方法。您将使用所需的结果值设置模拟,然后调用被测试的方法。然后您将断言您的方法执行了您想要的操作。
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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 |
一句警告。嘲笑是最有可能过分的。当你这样做的时候,它会使你的测试更长,更难理解,并且不可能维持。在模仿像
在http://blog.xelnor.net/python mocking datetime/中讨论了几种解决方案。综上所述:
模拟对象-简单有效但中断isInstance()检查:
1 2 3 4 | 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()) |
模拟课堂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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) |
用作:
1 2 | with mock_datetime_now(target, datetime): .... |
对于那些使用pytest和mocker的人来说,这里是我如何嘲笑
1 2 3 4 5 6 7 | test_get_now(mocker): datetime_mock = mocker.patch("blackline_accounts_import.datetime",) datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0) now == function_being_tested() # run function assert now == datetime.datetime(2019,3,11,6,2,0,0) |
实际上,必须设置mock以返回指定的日期。您不能直接修补datetime的对象。
这是模拟
1 2 3 4 5 6 7 8 9 10 11 | 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 |
注意
在不添加
1 2 3 4 5 6 7 8 | import mock from datetime import datetime from where_datetime_used import do initial_date = datetime.strptime('2018-09-27',"%Y-%m-%d") with mock.patch('where_datetime_used.datetime') as mocked_dt: mocked_dt.now.return_value = initial_date do() |
我使用自定义修饰符实现了@user3016183方法:
1 2 3 4 5 6 7 8 | 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 |
我想也许有一天能帮到别人…
也许您可以使用自己的"today()"方法,在需要的地方进行修补。模拟utcnow()的例子可以在这里找到:https://bitback.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?AT =默认值