これが機能しない理由を誰かに教えてもらえますか?
>>> 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)
おそらく誰かがより良い方法を提案できますか?
これが機能しない理由を誰かに教えてもらえますか?
>>> 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)
おそらく誰かがより良い方法を提案できますか?
回答:
いくつか問題があります。
まず第一に、あなたの使用方法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)
datetime
インスタンスを元の値に復元しますか?とdeepcoppy
?
patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
@patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29))))
。
別のオプションは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
何に価値があるかについては、モックのドキュメントで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)
...
from datetime import date
それはモジュールの名前であり、そこfrom datetime import date
への呼び出しがdate.today()
表示されるようです
私はこれに少し遅れて来たと思いますが、ここでの主な問題は、あなたが直接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
参照をテストファイルから隠す方法にあると思いました。
import datetime
かfrom datetime import strptime
?最初のものを実行している場合は、モックdatetime
して実行する必要mocked_datetime.strptime.return_value = whatever
があります。後者の場合は、テストしたメソッドが存在するファイル内のstrptime参照を直接モックする必要があります。
Mock(return_value=datetime...)
です。
ダニエル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)
date
/ datetime
自体、それは世界的に利用可能な変数を使用するため、問題はないはずです。dpaste.com/790310
私は数日前に同じ状況に直面しました。私の解決策は、テストするモジュールに関数を定義し、それを模擬することでした:
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)
私にとって最も簡単な方法はこれを行うことです:
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
停止します作業。
datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)
をに短縮することもできdatetime_mock.now.return_value = datetime(1999, 1, 1)
ます。でパッチを開始する代わりにstart()
、with patch(...):
コンテキストマネージャーを使用してdatetime
、テストが終了したときにが通常の(モック解除された)動作を再度行うことを検討してください。
datetime.datetime.now()
モックを解除する方法を意味します^^?
datetime module
からtarget_module
動作を停止します。
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()
関数をモックすることができます。
__instancecheck__
メソッドで最大の再帰深度RuntimeErrorを取得しています。
一般的に言って、あなたが持っているでしょう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
、本当にモックする必要があるかどうか自問してください。テストが短くてポイントに達していて、関数をモックしなくても問題なく機能する場合、モックする必要のあるオブジェクトではなく、テストしているコードの内部の詳細を見ているだけかもしれません。
モックオブジェクトが元のモジュールをラップするように構成されて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
モジュールに転送されます。
いくつかのソリューションが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):
....
多分あなたはあなたが必要なところにパッチを当てるあなた自身の "today()"メソッドを使うことができるでしょう。utcnow()をモックする例はここにあります:https ://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default
カスタムデコレータを使用して@ 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
いつか誰かを助けるかもしれないと思った...
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()
モッカーで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のオブジェクトに直接パッチを適用することはできません。
私はモックで必要なメソッドを実際のメソッドにインポートdatetime
しrealdatetime
て置き換えることでこの作業を行いました:
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)
あなたはあざけることができます 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
パッチデコレータには何がありますか?
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
、通常のオブジェクトを。
mock
ライブラリのドキュメント:voidspace.org.uk/python/mock/examples.html#partial-mocking