我使用的是家釀datetime.datetime
模擬修補了日期時間整個代碼(見最底部),但其他人似乎擊中問題的理解它是如何工作的,並擊中意外問題。考慮了以下測試:最好在Django單位修補日期時間的方法進行試驗,
@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()
這部分被執行。 由於我們在測試中使用未打補丁的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
對象被轉換爲 datetime.date
,當你從SQL檢索u2.last_login
,該值是 實際上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://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
謝謝!
https://github.com/spulec/freezegun適用於Django。 –
是的,謝謝。我在那裏找到的解決方案是使用一個元類,它改變'isinstance'的行爲,以便在執行'isinstance(datetime.datetime.now(),FakeDatetime)'時返回True。你能把這個作爲答案嗎,我可以接受嗎? – Jammer