Python 试图模拟 datetime.date.today(),但不起作用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4481954/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Trying to mock datetime.date.today(), but not working
提问by Belmin Fernandez
Can anyone tell me why this isn't working?
谁能告诉我为什么这不起作用?
>>> 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)
Perhaps someone could suggest a better way?
也许有人可以提出更好的方法?
采纳答案by Daniel G
There are a few problems.
有几个问题。
First of all, the way you're using mock.patchisn't quite right. When used as a decorator, it replaces the given function/class (in this case, datetime.date.today) with a Mockobject only within the decorated function. So, only within your today()will datetime.date.todaybe a different function, which doesn't appear to be what you want.
首先,你使用的方式mock.patch不太对。当用作装饰器时,它将给定的函数/类(在本例中为datetime.date.today)替换为仅在装饰函数内的Mock对象。因此,只有在您的内部才会有一个不同的功能,这似乎不是您想要的。today()datetime.date.today
What you really want seems to be more like this:
你真正想要的似乎更像这样:
@mock.patch('datetime.date.today')
def test():
datetime.date.today.return_value = date(2010, 1, 1)
print datetime.date.today()
Unfortunately, this won't work:
不幸的是,这行不通:
>>> 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'
This fails because Python built-in types are immutable - see this answerfor more details.
这失败了,因为 Python 内置类型是不可变的 -有关更多详细信息,请参阅此答案。
In this case, I would subclass datetime.date myself and create the right function:
在这种情况下,我会自己继承 datetime.date 并创建正确的函数:
import datetime
class NewDate(datetime.date):
@classmethod
def today(cls):
return cls(2010, 1, 1)
datetime.date = NewDate
And now you could do:
现在你可以这样做:
>>> datetime.date.today()
NewDate(2010, 1, 1)
回答by eternicode
To add to Daniel G's solution:
添加到Daniel G 的解决方案中:
from datetime import date
class FakeDate(date):
"A manipulable date replacement"
def __new__(cls, *args, **kwargs):
return date.__new__(date, *args, **kwargs)
This creates a class which, when instantiated, will return a normal datetime.date object, but which is also able to be changed.
这将创建一个类,该类在实例化时将返回一个正常的 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)
回答by Konstantine Rybnikov
Maybe you could use your own "today()" method that you will patch where needed. Example with mocking utcnow() can be found here: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default
也许您可以使用您自己的“today()”方法,您将在需要的地方修补该方法。可以在此处找到模拟 utcnow() 的示例:https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default
回答by jpmc26
Generally speaking, you would have datetimeor perhaps datetime.dateimported into a module somewhere. A more effective way of mocking the method would be to patch it on the module that is importing it. Example:
一般来说,您可能已经datetime或可能datetime.date导入到某个模块中。模拟该方法的更有效方法是在导入它的模块上修补它。例子:
a.py
一个.py
from datetime import date
def my_method():
return date.today()
Then for your test, the mock object itself would be passed as an argument to the test method. You would set up the mock with the result value you want, and then call your method under test. Then you would assert that your method did what you want.
然后对于您的测试,模拟对象本身将作为参数传递给测试方法。您将使用您想要的结果值设置模拟,然后调用您的测试方法。然后你会断言你的方法做了你想要的。
>>> 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
A word of warning. It is most certainly possible to go overboard with mocking. When you do, it makes your tests longer, harder to understand, and impossible to maintain. Before you mock a method as simple as datetime.date.today, ask yourself if you really needto mock it. If your test is short and to the point and works fine without mocking the function, you may just be looking at an internal detail of the code you're testing rather than an object you need to mock.
一句警告。毫无疑问,嘲笑是过分的。当您这样做时,它会使您的测试变得更长、更难理解且无法维护。在模拟像 一样简单的方法之前datetime.date.today,先问问自己是否真的需要模拟它。如果您的测试简短且切题并且在不模拟函数的情况下运行良好,则您可能只是查看正在测试的代码的内部细节,而不是需要模拟的对象。
回答by Mehdi Behrooz
Another option is to use https://github.com/spulec/freezegun/
另一种选择是使用 https://github.com/spurec/freezegun/
Install it:
安装它:
pip install freezegun
And use it:
并使用它:
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
It also affects other datetime calls in method calls from other modules:
它还会影响来自其他模块的方法调用中的其他日期时间调用:
other_module.py:
其他_module.py:
from datetime import datetime
def other_method():
print(datetime.now())
main.py:
主要.py:
from freezegun import freeze_time
@freeze_time("2012-01-01")
def test_something():
import other_module
other_module.other_method()
And finally:
最后:
$ python main.py
# 2012-01-01
回答by iferminm
I guess I came a little late for this but I think the main problem here is that you're patching datetime.date.today directly and, according to the documentation, this is wrong.
我想我来晚了一点,但我认为这里的主要问题是您直接修补 datetime.date.today 并且根据文档,这是错误的。
You should patch the reference imported in the file where the tested function is, for example.
例如,您应该修补导入到测试函数所在文件中的引用。
Let's say you have a functions.py file where you have the following:
假设您有一个 functions.py 文件,其中包含以下内容:
import datetime
def get_today():
return datetime.date.today()
then, in your test, you should have something like this
那么,在你的测试中,你应该有这样的东西
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...
Hope this helps a little bit.
希望这会有所帮助。
回答by Andrey Lebedev
You can use the following approach, based on Daniel G solution. This one has advantage of not breaking type checking with isinstance(d, datetime.date).
您可以使用以下方法,基于 Daniel G 解决方案。这个的优点是不破坏类型检查isinstance(d, datetime.date)。
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)
Basically, we replace C-based datetime.dateclass with our own python subclass, that produces original datetime.dateinstances and responds to isinstance()queries exactly as native datetime.date.
基本上,我们datetime.date用我们自己的 python 子类替换基于 C 的类,它生成原始datetime.date实例并isinstance()完全像 native 一样响应查询datetime.date。
Use it as context manager in your tests:
在您的测试中将其用作上下文管理器:
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)
Similar approach can be used to mock datetime.datetime.now()function.
类似的方法可用于模拟datetime.datetime.now()函数。
回答by kpup
For what it's worth, the Mock docs talk about datetime.date.today specifically, and it's possible to do this without having to create a dummy class:
对于它的价值,Mock 文档专门讨论了 datetime.date.today,并且无需创建虚拟类就可以做到这一点:
https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking
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)
...
回答by DainDwarf
I implemented @user3016183 method using a custom decorator:
我使用自定义装饰器实现了 @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
I thought that might help someone one day...
我以为有一天这可能会帮助某人...
回答by eddygeek
Several solutions are discussed in http://blog.xelnor.net/python-mocking-datetime/. In summary:
http://blog.xelnor.net/python-mocking-datetime/中讨论了几种解决方案。总之:
Mock object- Simple and efficient but breaks isinstance() checks:
模拟对象- 简单高效,但会破坏 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())
Mock class
模拟课
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)
Use as:
用于:
with mock_datetime_now(target, datetime):
....

