testing %s - Python:試圖模擬datetime.date.today()但不工作




now format (11)

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

誰能告訴我為什麼這不起作用?

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

也許有人可以提出更好的方法?


一般來說,你可能會在某個地方將datetime或者datetime.date導入到模塊中。 模擬該方法的更有效方法是將其修補到正在導入它的模塊上。 例:

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.today這樣簡單的方法之前,問問你自己是否真的需要嘲笑它。 如果你的測試很短,並且沒有嘲諷功能,那麼你可能只是在看你正在測試的代碼的內部細節,而不是你需要模擬的對象。


另一種選擇是使用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

它還影響來自其他模塊的方法調用中的其他日期時間調用:

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

要添加到丹尼爾G的解決方案:

from datetime import date

class FakeDate(date):
    "A manipulable date replacement"
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

這創建了一個類,在實例化時,它將返回一個正常的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,根據文檔,這是錯誤的。

例如,您應該將導入的參考文件修補到測試函數所在的文件中。

比方說,你有一個functions.py文件,你有以下幾點:

import datetime

def get_today():
    return datetime.date.today()

那麼,在你的測試中,你應該有這樣的東西

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

希望這有點幫助。


對我來說最簡單的方法就是這樣做:

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


幾天前我面臨同樣的情況,我的解決方案是在模塊中定義一個函數來測試並嘲笑:

def get_date_now():
    return datetime.datetime.now()

今天我發現了FreezeGun ,它似乎FreezeGun地覆蓋了這個案例

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解決方案,您可以使用以下方法。 這個有利於不使用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)

基本上,我們用我們自己的python子類替換基於C的datetime.date類,它產生原始的datetime.date實例,並且對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()函數。


有幾個問題。

首先,你使用mock.patch的方式不太正確。 當用作裝飾器時,它僅在裝飾函數中用Mock對象替換給定的函數/類(在本例中為datetime.date.today )。 所以,只有在你的today()datetime.date.today是一個不同的功能,這似乎並不是你想要的。

你真正想要的東西似乎更像這樣:

@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內置類型是不可變的 - 請參閱此答案以獲取更多詳細信息。

在這種情況下,我會自定義datetime.date的子類並創建正確的函數:

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)

其他人已經解釋了元類如何工作以及它們如何適合Python類型系統。 以下是它們可用於什麼的示例。 在我編寫的測試框架中,我想跟踪定義類的順序,以便稍後我可以按此順序實例化它們。 我發現使用元類這樣做最容易。

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

然後,任何屬於MyType子類的東西都會獲得一個類屬性_order ,它記錄了類的定義順序。





python testing datetime mocking