2014-04-01 19 views
3

我使用的是家釀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) 

Source

您撥打測試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 

謝謝!

+1

https://github.com/spulec/freezegun適用於Django。 –

+0

是的,謝謝。我在那裏找到的解決方案是使用一個元類,它改變'isinstance'的行爲,以便在執行'isinstance(datetime.datetime.now(),FakeDatetime)'時返回True。你能把這個作爲答案嗎,我可以接受嗎? – Jammer

回答

相關問題