Как изменить дату / время в Python для всех модулей? - PullRequest
20 голосов
/ 17 апреля 2010

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

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

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

Пока я нашел два решения:

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

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

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

Ответы [ 6 ]

9 голосов
/ 17 апреля 2010

Monkey-patching 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(), чтобы получить поддельные результаты времени.

Обновление: Спустя годы есть альтернатива, как отмечено в Ответ Дейва Форгака .

6 голосов
/ 12 июля 2017

Пакет 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

2 голосов
/ 16 января 2013

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

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

2 голосов
/ 17 апреля 2010

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

1 голос
/ 17 апреля 2010

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

что-то вроде

import time
import datetime

class MyDatetime:

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

datetime.datetime = MyDatetime

print datetime.datetime().now()
0 голосов
/ 17 апреля 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...