谁能告诉我为什么这行不通?

>>> 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)

也许有人能提出一个更好的办法?


当前回答

您可以使用以下基于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)

基本上,我们取代了基于c语言的datetime。Date类,使用我们自己的python子类,生成原始的datetime。日期实例,并响应isinstance()查询完全与本机datetime.date相同。

在测试中使用它作为上下文管理器:

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()函数。

其他回答

您可以使用以下基于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)

基本上,我们取代了基于c语言的datetime。Date类,使用我们自己的python子类,生成原始的datetime。日期实例,并响应isinstance()查询完全与本机datetime.date相同。

在测试中使用它作为上下文管理器:

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()函数。

另一种选择是使用 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

它还会影响其他模块的方法调用中的其他datetime调用:

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

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):
   ....

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对象了。

一般来说,你会有datetime或datetime。将日期导入某个模块。模拟该方法的一种更有效的方法是在导入它的模块上修补它。例子:

a.py

from datetime import date

def my_method():
    return date.today()

然后,对于您的测试,模拟对象本身将作为参数传递给测试方法。您将使用所需的结果值设置模拟,然后调用测试中的方法。然后您可以断言您的方法完成了您想要的。

>>> 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

警告一句。很有可能嘲讽得过火了。当您这样做时,它会使您的测试更长、更难理解,并且不可能维护。在模拟像datetime.date这样简单的方法之前。今天,问问自己是否真的需要嘲笑它。如果您的测试短小精干,并且在不模拟函数的情况下运行良好,那么您可能只看到了正在测试的代码的内部细节,而不是需要模拟的对象。