Подтвердить что ты не робот

Как изменить дату/время в Python для всех модулей?

Когда я пишу с помощью бизнес-логики, мой код часто зависит от текущего времени. Например, алгоритм, который смотрит на каждый незавершенный порядок и проверяет, следует ли отправлять счет-фактуру (что зависит от количества дней с момента окончания работы). В этих случаях создание счета-фактуры не инициируется явным действием пользователя, а фоновым заданием.

Теперь это создает проблему для меня, когда дело доходит до тестирования:

  • Я могу легко протестировать создание счета-фактуры.
  • Однако сложно создать заказ в тесте и проверить, что задание фона определяет правильные заказы в нужное время.

До сих пор я нашел два решения:

  • В тестовой настройке вычислите даты работы относительно текущей даты. Недостаток: код становится довольно сложным, поскольку больше нет явных дат. Иногда бизнес-логика довольно сложна для краевых случаев, поэтому становится трудно отлаживаться из-за всех этих относительных дат.
  • У меня есть собственные функции доступа к дате/времени, которые я использую во всем моем коде. В тесте я просто установил текущую дату, и все модули получают эту дату. Поэтому я могу смоделировать создание заказа в феврале и проверить, что счет-фактура создается в апреле легко. Downside: сторонние модули не используют этот механизм, поэтому очень сложно интегрировать + протестировать их.

Второй подход был для меня более успешным. Поэтому я ищу способ установить время возвращения Python datetime + модулей времени. Обычно установить дату достаточно, мне не нужно устанавливать текущий час или секунду (даже если это было бы хорошо).

Есть ли такая утилита? Есть ли (внутренний) Python API, который я могу использовать?

4b9b3361

Ответ 1

Обезьяна-паттинг time.time, вероятно, достаточна, фактически, поскольку она обеспечивает основу почти для всех других временных подпрограмм в Python. Это, похоже, очень хорошо подходит для вашего использования, не прибегая к более сложным трюкам, и не имеет значения, когда вы это делаете (кроме нескольких пакетов stdlib, таких как Queue.py и threading.py, которые делают from 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()

Теперь в моем коде приложения я просто делаю import apptime as time; time.time(), чтобы получить текущее значение времени, тогда как в тестовом коде я могу сначала сделать apptime.time = MyTimeService(mock_time_func) в моем setUp() коде, чтобы предоставить результаты поддельного времени.

Ответ 2

Вы можете исправить систему, создав пользовательский модуль datetime (даже поддельный - см. пример ниже), действующий как прокси-сервер, а затем вставьте его в словарь 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 - очень динамичный язык, где вы можете делать довольно много интересных вещей, но исправление кода таким образом всегда имеет определенную степень риска, даже если мы говорим здесь о тестовом коде.
Но в основном, я думаю, что вспомогательная функция сделает более точную проверку теста, а также ваш код будет более явным с точки зрения того, что он будет протестирован, что повысит читаемость. Поэтому, если изменение не слишком дорого, я бы пошел на второй подход.

Ответ 3

Я использовал бы помощников из пакета testfixtures, чтобы издеваться над датами, датами и временем, которые вы делаете:

http://packages.python.org/testfixtures/datetime.html

Ответ 4

Пакет freezegun был специально создан для этой цели. Это позволяет вам изменить дату тестирования кода. Его можно использовать напрямую или через декоратора или менеджера контекста. Один пример:

from freezegun import freeze_time
import datetime

@freeze_time("2012-01-14")
def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)

Дополнительные примеры см. в проекте: https://github.com/spulec/freezegun

Ответ 5

Ну, один из способов сделать это - динамическое патч для модуля time/datetime

что-то вроде

import time
import datetime

class MyDatetime:

    def now(self):
        return time.time()

datetime.datetime = MyDatetime

print datetime.datetime().now()

Ответ 6

может быть несколько способов сделать это, например создание заказов (с текущей меткой времени), а затем изменение этого значения в БД непосредственно каким-то внешним процессом (при условии, что данные находятся в БД).

Я предлагаю что-то еще. Вы хотя бы запустили приложение на виртуальной машине, установив время, чтобы сказать Feb, создав заказы, а затем просто изменив время виртуальной машины? Этот подход является самым близким, поскольку вы можете добраться до реальной ситуации.