Когда я пишу с бизнес-логикой, мой код часто зависит от текущего времени. Например, алгоритм, который смотрит на каждый незаконченный порядок и проверяет, должен ли счет быть отправлен (который не зависит ни в какие изо дней начиная с, задание было закончено). В этих случаях, создающих счет, не инициирован явным пользовательским действием, но фоновым заданием.
Теперь это создает проблему для меня когда дело доходит до тестирования:
До сих пор я нашел два решения:
Второй подход был путем, более успешным мне, в конце концов. Поэтому я ищу способ установить время datetime+time возврат модулей Python. Назначение даты обычно достаточно, я не должен устанавливать текущий час или второй (даже при том, что это было бы хорошо).
Есть ли такая утилита? Существует ли (внутренний) Python API, который я могу использовать?
Monkey-patching time.time
, вероятно, на самом деле достаточно, поскольку он обеспечивает основу почти для всех других основанных на времени подпрограмм в Python. Похоже, что это довольно хорошо справляется с вашим вариантом использования, не прибегая к более сложным трюкам, и не имеет значения, когда вы это делаете (за исключением нескольких пакетов stdlib, таких как Queue.py и threading.py, которые делают из time import time
, и в этом случае вы должны пропатчить их до того, как они будут импортированы):
>>> import datetime
>>> datetime.datetime.now()
datetime.datetime(2010, 4, 17, 14, 5, 35, 642000)
>>> import time
>>> def mytime(): return 120000000.0
...
>>> time.time = mytime
>>> datetime.datetime.now()
datetime.datetime(1973, 10, 20, 17, 20)
Тем не менее, за годы имитации объектов для различных типов автоматического тестирования мне нужен был этот подход очень редко, так как в большинстве случаев он мой собственный код приложения, которому нужны насмешки, а не подпрограммы stdlib. В конце концов, вы знаете, что они уже работают. Если вы сталкиваетесь с ситуациями, когда ваш собственный код должен обрабатывать значения, возвращаемые библиотечными подпрограммами, вы можете имитировать сами библиотечные подпрограммы, по крайней мере, при проверке того, как ваше собственное приложение будет обрабатывать временные метки.
На сегодняшний день наилучшим подходом является создание вашей собственной процедуры (подпрограмм) обслуживания даты / времени, которую вы используете исключительно в коде вашего приложения, и встроенной в нее способности тестов выдавать ложные результаты по мере необходимости.Например, я иногда делаю более сложный эквивалент этого:
# in file apptime.py (for example)
import time as _time
class MyTimeService(object):
def __init__(self, get_time=None):
self.get_time = get_time or _time.time
def __call__(self):
return self.get_time()
time = MyTimeService()
Теперь в моем коде приложения я просто импортирую apptime как время; time.time ()
, чтобы получить текущее значение времени, тогда как в тестовом коде я могу сначала выполнить apptime.time = MyTimeService (mock_time_func)
в моем коде setUp ()
, чтобы предоставить поддельные результаты времени.
Обновление: Спустя годы есть альтернатива, как отмечено в ответе Дэйва Форгака .
Вы можете исправить систему, создав собственный модуль даты и времени (даже поддельный - см. пример ниже) в качестве прокси, а затем вставьте его в словарь sys.modules. С этого момента каждый импорт в модуль datetime будет возвращать ваш прокси.
В отношении класса datetime все еще есть предостережение, особенно когда кто-то делает from datetime import datetime
; для этого вы можете просто добавить еще один прокси только для этого класса.
Вот пример того, что я говорю - конечно, это просто то, что я бросил за 5 минут, и может иметь несколько проблем (например, тип класса datetime неверен); но, надеюсь, это уже может быть полезно.
import sys
import datetime as datetime_orig
class DummyDateTimeModule(sys.__class__):
""" Dummy class, for faking datetime module """
def __init__(self):
sys.modules["datetime"] = self
def __getattr__(self, attr):
if attr=="datetime":
return DummyDateTimeClass()
else:
return getattr(datetime_orig, attr)
class DummyDateTimeClass(object):
def __getattr__(self, attr):
return getattr(datetime_orig.datetime, attr)
dt_fake = DummyDateTimeModule()
Наконец, стоит ли оно того?
Честно говоря, второе решение мне нравится больше, чем это :-).
Да, python - очень динамичный язык, на котором вы можете делать довольно много интересных вещей, но исправление кода таким образом всегда сопряжено с определенным риском, даже если мы говорим здесь о тестировании код.
Но в основном, я думаю, что вспомогательная функция сделает исправление тестов более явным, а также ваш код будет более ясным с точки зрения того, что он будет тестироваться, что повысит удобочитаемость.
Поэтому, если изменение не слишком дорогое, я воспользуюсь вашим вторым подходом.
для этого может быть несколько способов, например, создание заказов (с текущей меткой времени) и последующее изменение этого значения в БД напрямую каким-либо внешним процессом (при условии, что данные находятся в БД).
Я предлагаю кое-что еще. Вы когда-нибудь задумывались о том, чтобы запустить приложение на виртуальной машине, установить время, чтобы сказать «февраль», создать заказы, а затем просто изменить время виртуальных машин? Этот подход максимально приближен к реальной жизненной ситуации.
Один из способов сделать это - динамически исправить модуль времени / даты и времени
, например,
import time
import datetime
class MyDatetime:
def now(self):
return time.time()
datetime.datetime = MyDatetime
print datetime.datetime().now()