web-dev-qa-db-ja.com

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)

おそらく誰かがより良い方法を提案できますか?

130

いくつかの問題があります。

まず第一に、mock.patchの使用方法は適切ではありません。デコレータとして使用する場合、指定された関数/クラス(この場合はdatetime.date.today)を、装飾された関数内でのみMockオブジェクトに置き換えます。したがって、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)
105
Daniel G

別のオプションは 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
136
Mehdi Behrooz

Mockのドキュメントでは、その価値について具体的にdatetime.date.todayについて説明しています。ダミークラスを作成せずにこれを行うことができます。

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)
...
87
kpup

私はこれに少し遅れて来たと思いますが、ここでの主な問題は、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...

これが少し役立つことを願っています。

32
iferminm

ダニエル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)
29
eternicode

私は数日前に同じ状況に直面し、私の解決策は、テストするモジュールで関数を定義し、それをモックすることでした:

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

今日、私は 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)
6
Hito_kun

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.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()関数をモックするために、同様のアプローチを使用できます。

5
Andrey Lebedev

私にとって最も簡単な方法はこれを行うことです:

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

このソリューションの注意:datetime moduletarget_moduleのすべての機能は動作を停止します。

4
frx08

一般的には、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

警告の言葉。 certainly笑で船外に行くことは最も確かに可能です。実行すると、テストが長くなり、理解しにくくなり、維持できなくなります。 datetime.date.todayのような単純なメソッドをモックする前に、実際に必要をモックするかどうかを自問してください。テストが短く、機能をモックせずに正常に機能する場合、モックする必要のあるオブジェクトではなく、テストしているコードの内部の詳細を見るだけです。

3
jpmc26

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

Pytestをmockerで使用している方は、datetime.datetime.now()をモックした方法がここにあります。これは元の質問と非常によく似ています。

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)

基本的に、指定された日付を返すようにモックを設定する必要があります。 datetimeのオブジェクトに直接パッチを適用することはできません。

0
Daniel Butler

カスタムデコレータを使用して@ 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

いつか誰かを助けるかもしれないと思った...

0
DainDwarf

datetimerealdatetimeとしてインポートし、モックで必要なメソッドを実際のメソッドに置き換えて、この作業を行いました。

import datetime as realdatetime

@mock.patch('datetime')
def test_method(self, mock_datetime):
    mock_datetime.today = realdatetime.today
    mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)
0
Adam McKenna

モックオブジェクトは元のdatetimeモジュールをラップするように構成されているため、残りのdatetime関数が引き続き機能するというボーナスを追加して、datetime.date.today()をモックする別の方法があります。

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

mock.patch()へのwraps=datetime引数に注意してください– foo_moduledate.today()以外の他のdatetime関数を使用する場合、元のラップされたdatetimeモジュールに転送されます。

0
mrts

必要に応じてパッチを適用する独自の「today()」メソッドを使用できます。 utcnow()をモックする例はここにあります: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at =デフォルト

0

side_effectsを追加せずにdatetimeモジュールから関数をモックすることができます。

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()
0
Daniil Mashkin