datetime.date.today()をモックしようとしていますが、機能しません


158

これが機能しない理由を誰かに教えてもらえますか?

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

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


1
mockライブラリのドキュメント:voidspace.org.uk/python/mock/examples.html#partial-mocking
guettli

回答:


124

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

まず第一に、あなたの使用方法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)

13
素晴らしい解決策ですが、残念ながら酸洗いの問題が発生します。
Baczek

14
この答えは良いですが、それはクラスを作成せずに日時を模擬することが可能です:stackoverflow.com/a/25652721/117268
エミルStenström

どのようにdatetimeインスタンスを元の値に復元しますか?とdeepcoppy
Oleg Belousov 2017年

5
はるかに簡単:patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
Victor Gavro 2017年

1
より簡単に実行できます@patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29))))
Jonhy Beebop 2018

162

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

13
非常に便利なライブラリ
Shaun

3
フリーズガンテストの実行が遅いことに気付いた場合は、python-libfaketimeを試すこともできます。
Simon Weber、

素晴らしいライブラリですが、残念ながらGoogle App Engine NDB / Datastoreではうまく機能しません。
ブランドン2016年

「freezegun」が図書館の名前だというのが大好きです。私はPython開発者が大好きです!:-D
MikeyE 2018

動作しますが、フリーズガンは遅いようです。特に、現在の時間に複数の呼び出しを伴う複雑なロジックがある場合はそうです。
Andrey Belyak

115

何に価値があるかについては、モックのドキュメントで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)
...

2
これは実際にはうまくいきませんでした。エントリーを見つける努力に感謝します。
Pradyot 2015年

8
パッチ機能での「mymodule」の意味は何ですか?
seufagner 2015

4
このリンク「部分的なモッキング」の下にあります
レオCハン

3
@seufagner mymoduleは、ややわかりにくい方法でvoidspace.org.uk/python/mock/patch.html#where-to-patchで説明されています。あなたのモジュールが使用している場合、from datetime import dateそれはモジュールの名前であり、そこfrom datetime import dateへの呼び出しがdate.today()表示されるようです
danio

1
ありがとう。働きました!例:mock.patch( 'tests.views.datetime')をmock_dateとして:mock_date.today.return_value = datetime.datetime(2016、9、18)mock_date.side_effect = lambda * args、** kw:date(* args 、** kw)
Latrova 2016

36

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

これが少し役に立てば幸いです。


これは非常に説得力がありますが、実行できません(をスローしますNameError: name 'datetime' is not defined)。テストファイルにインポートしていない場合、datetime.strptime参照はMock(return_value=...)どこから来datetimeますか?更新:OK、私は先に進んでdatetimeモジュールをテストファイルにインポートしました。その秘訣は、datetime参照をテストファイルから隠す方法にあると思いました。
imrek

@DrunkenMaster私はあなたが何をしていたのか、そしてどの参照をあざ笑っているのかの例を見なければなりません。あなたは何をしていましたimport datetimefrom datetime import strptime?最初のものを実行している場合は、モックdatetimeして実行する必要mocked_datetime.strptime.return_value = whateverがあります。後者の場合は、テストしたメソッドが存在するファイル内のstrptime参照を直接モックする必要があります。
iferminm

@israelord私が言ったことは、最後のコードスニペット(テストファイル)には、日時参照が機能するためのインポートが欠落しているということMock(return_value=datetime...)です。
imrek 2017年

32

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

2
ここでは非常に注意してください-fromバージョンを使用する必要があります。そうしないと、datetime.date(またはdatetimeなど)を使用すると奇妙になる可能性があります。IE-偽の新しいものがそれ自体を呼び出したときにスタックの深さに達しました
ダニーステープル2012

偽のオブジェクトが独自のモジュールdpaste.com/790309にある場合は、この問題は発生しません。それは嘲笑関数と同じモジュールでいたとしてもかかわらず、それはインポートしませんdate/ datetime自体、それは世界的に利用可能な変数を使用するため、問題はないはずです。dpaste.com/790310
eternicode

簡潔な説明はここにあります:williamjohnbert.com/2011/07/…– ezdazuzena '08
08/13

9

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

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)

9

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

import datetime
from unittest.mock import Mock, patch

def test():
    datetime_mock = Mock(wraps=datetime.datetime)
    datetime_mock.now.return_value = datetime.datetime(1999, 1, 1)
    with patch('datetime.datetime', new=datetime_mock):
        assert datetime.datetime.now() == datetime.datetime(1999, 1, 1)

このソリューションのための注意:からのすべての機能datetime moduleからtarget_module停止します作業。


1
これは本当に素晴らしくて簡潔です。この線datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)をに短縮することもできdatetime_mock.now.return_value = datetime(1999, 1, 1)ます。でパッチを開始する代わりにstart()with patch(...):コンテキストマネージャーを使用してdatetime、テストが終了したときにが通常の(モック解除された)動作を再度行うことを検討してください。
Dirk

常に組み込みライブラリを利用するソリューションを支持する
Nam G VU

@ frx08このモックをリセットする方法を教えてください。私はdatetime.datetime.now()モックを解除する方法を意味します^^?
Nam G VU

この解決のための1つの注意からのすべての機能です-さて、このモックを使用しようとした後datetime moduleからtarget_module動作を停止します。
Nam G VU

1
@ frx08 with()が痛みを和らげることに同意します。たとえば、日付などのすべてのブロックの内部では、timedeltaは機能しなくなります。いまモックが必要なのに、日付の計算がまだ続いている場合はどうなりますか?申し訳ありませんが、.now()はモックされていない必要があります。
Nam G VU

7

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()関数をモックすることができます。


これがPython 2.7で機能するかどうかはわかりません。__instancecheck__メソッドで最大の再帰深度RuntimeErrorを取得しています。
Dan Loewenherz 14

これは実際にPython 2.7で機能し、インスタンスタイプチェックに関する私の問題を解決しました。ありがとう!
Karatheodory 2017

4

一般的に言って、あなたが持っているでしょう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、本当にモックする必要があるかどうか自問してください。テストが短くてポイントに達していて、関数をモックしなくても問題なく機能する場合、モックする必要のあるオブジェクトではなく、テストしているコードの内部の詳細を見ているだけかもしれません。


2

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

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

wraps=datetime引数に注意してくださいmock.patch()- foo_moduleが他のdatetime関数を使用する場合、date.today()それらは元のラップされたdatetimeモジュールに転送されます。


1
良い答えです。日付時刻モジュールを使用する必要がある日付をモックする必要があるほとんどのテスト
Antoine Vo

1

いくつかのソリューションが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):
   ....


0

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

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

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

モッカーでpytestを使用している人のために、ここで私がモックし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

私はモックで必要なメソッドを実際のメソッドにインポート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

あなたはあざけることができます datetimeこれを使ってする:

モジュール内sources.py

import datetime


class ShowTime:
    def current_date():
        return datetime.date.today().strftime('%Y-%m-%d')

あなたのtests.py

from unittest import TestCase, mock
import datetime


class TestShowTime(TestCase):
    def setUp(self) -> None:
        self.st = sources.ShowTime()
        super().setUp()

    @mock.patch('sources.datetime.date')
    def test_current_date(self, date_mock):
        date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
        current_date = self.st.current_date()
        self.assertEqual(current_date, '2019-10-01')

sourcesパッチデコレータには何がありますか?
エレナ

@elena様、私がほぼ1年前に考えていたことを思い出すことはかなり難しいことです))。アプリソースの任意のモジュール-アプリケーションのコードだけを意味していると思います。
MTMobile

0

CPythonは実際には、純粋なPython Lib / datetime.pyとCに最適化されたModules / _datetimemodule.cの両方を使用して、datetimeモジュールを実装しています。C最適化バージョンにはパッチを適用できませんが、pure-Pythonバージョンにはパッチを適用できます。

Lib / datetime.pyの pure-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、通常のオブジェクトを。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.