Лучший способ исправления даты и времени в модульных тестах Django
Я использую домашний напиток datetime.datetime
макет для исправления даты и времени по всему коду (см. в самом низу), но другие люди, похоже, сталкиваются с проблемами, понимая, как это работает, и сталкиваются с неожиданными проблемами. Рассматривается следующий тест:
@patch("datetime.datetime", FakeDatetime)
def my_test(self):
FakeDatetime.now_value = datetime(2014, 04, 02, 13, 0, 0)
u = User.objects.get(x=y)
u.last_login = datetime(2014, 04, 01, 14, 0, 0)
u.save()
u2 = User.objects.get(x=y)
# Checks if datetime.datetime.now() - u2.last_login < 24 hours
self.assertTrue(u2.logged_in_in_last_24_hours())
Теперь, если вы посмотрите, как Django DatetimeField сериализует даты в SQL:
def to_python(self, value):
if value is None:
return value
if isinstance(value, datetime.datetime):
return value
if isinstance(value, datetime.date):
value = datetime.datetime(value.year, value.month, value.day)
Эта часть исполняется, как вы звоните u.save()
в тесте. Как этот момент в значении кода Django value
(u.last_login
) имеет тип datetime.datetime
потому что мы присвоили значение в тесте, используя непатченную версию datetime (поскольку наш импорт находится на уровне модуля, а исправление - на уровне метода).
Теперь в коде Django, datetime.datetime
исправлен, поэтому:
isinstance(value, datetime.datetime)
эквивалентно:
isinstance(datetime.datetime(2014, 04, 01, 14, 0, 0), FakeDatetime)
что неверно, но:
isinstance(datetime.datetime(2014, 04, 01, 14, 0, 0), datetime.date)
верно, следовательно, datetime.datetime
объект превращается вdatetime.date
и когда вы получите u2.last_login
из SQL, значение на самом деле datetime(2014, 04, 01, 0, 0, 0)
и не datetime(2014, 04, 01, 14, 0, 0)
Следовательно, тесты не пройдены.
Способ обойти это заменить:
u.date_joined = datetime(2014, 04, 01, 14, 0, 0)
с:
u.date_joined = FakeDatetime(2014, 04, 01, 14, 0, 0)
но это кажется склонным к ошибкам и приводит в замешательство людей, использующих или пишущих тесты.
Особенно в тех случаях, когда вам нужен настоящий now
значение, которое вы должны сделать datetime_to_fakedatetime(datetime.datetime.now())
или позвоните по телефону FakeDatetime.now()
но убедитесь, что предыдущий тест FakeDatetime.now_value
,
Я ищу способ сделать это более интуитивным, но в то же время избегать необходимости исправления datetime.datetime
объекты в определенных подмодулях (их может быть много), и просто исправьте их по всему коду.
Код для доморощенного макета:
from datetime import datetime
class FakeDatetime(datetime):
now_value = None
def __init__(self, *args, **kwargs):
return super(FakeDatetime, self).__init__()
@classmethod
def now(cls):
if cls.now_value:
result = cls.now_value
else:
result = datetime.now()
return datetime_to_fakedatetime(result)
@classmethod
def utcnow(cls):
if cls.now_value:
result = cls.now_value
else:
result = datetime.utcnow()
return datetime_to_fakedatetime(result)
# http://stackru.com/questions/20288439/how-to-mock-the-operator-in-python-specifically-datetime-date-datetime-ti
def __add__(self, other):
return datetime_to_fakedatetime(super(FakeDatetime, self).__add__(other))
def __sub__(self, other):
return datetime_to_fakedatetime(super(FakeDatetime, self).__sub__(other))
def __radd__(self, other):
return datetime_to_fakedatetime(super(FakeDatetime, self).__radd__(other))
def __rsub__(self, other):
return datetime_to_fakedatetime(super(FakeDatetime, self).__rsub__(other))
def datetime_to_fakedatetime(dt):
# Because (datetime - datetime) produces a timedelta, so check if the result is of the correct type.
if isinstance(dt, datetime):
return FakeDatetime(
dt.year,
dt.month,
dt.day,
dt.hour,
dt.minute,
dt.second,
dt.microsecond,
dt.tzinfo
)
return dt
Спасибо!
1 ответ
Существует https://github.com/spulec/freezegun который работает с Django.