Попытка смоделировать 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)
Возможно, кто-то может предложить лучший способ?
Ответы
Ответ 1
Есть несколько проблем.
Прежде всего, способ использования 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)
Ответ 2
Другой вариант - использовать
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
Ответ 3
Что бы это ни стоило, в документах 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)
...
Ответ 4
Думаю, я немного опоздал на это, но я думаю, что основная проблема заключается в том, что вы исправляете 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...
Надеюсь, это немного поможет.
Ответ 5
Чтобы добавить в решение Daniel 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)
Ответ 6
Вы можете использовать следующий подход, основанный на решении 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)
В принципе, мы заменяем класс datetime.date
на основе C нашим собственным подклассом python, который создает оригинальные экземпляры datetime.date
и отвечает на запросы isinstance()
точно так же, как native 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()
функции.
Ответ 7
Я столкнулся с такой же ситуацией пару дней назад, и мое решение состояло в том, чтобы определить функцию в модуле для тестирования и просто посмеяться над этим:
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)
Ответ 8
Самый простой способ для меня это сделать:
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 module
из target_module
перестанут работать.
Ответ 9
Вообще говоря, у вас есть 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
, спросите себя, действительно ли вам нужно издеваться над этим. Если ваш тест является коротким и точным и работает отлично, не издеваясь над функцией, вы можете просто посмотреть внутреннюю деталь тестируемого кода, а не объект, который вам нужно высмеять.
Ответ 10
Несколько решений обсуждаются в http://blog.xelnor.net/python-mocking-datetime/. Вкратце:
Mock object - простой и эффективный, но прерывает проверку 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())
Класс mock
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):
....
Ответ 11
Возможно, вы можете использовать свой собственный метод "today()", который вы будете исправлять там, где это необходимо. Пример с mocking utcnow() можно найти здесь: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default
Ответ 12
Я применил метод @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
Я думал, что однажды это может помочь кому-то...
Ответ 13
Можно макетировать функции из модуля 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()
Ответ 14
Вот еще один способ смоделировать 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
.
Ответ 15
Для тех из вас, кто использует 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.
Ответ 16
Я сделал эту работу, импортировав 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)
Ответ 17
Вы можете издеваться над 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')