4

私は自作のdatetime.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)

は True であるため、datetime.datetimeオブジェクトは に変換され 、SQL からdatetime.date取得すると、値は実際には ではなくu2.last_logindatetime(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://stackoverflow.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

ありがとう!

4

1 に答える 1

3

Django で動作するhttps://github.com/spulec/freezegunがあります。

于 2014-04-01T16:01:19.717 に答える