2010-04-17 94 views
18

當我使用業務邏輯編寫代碼時,我的代碼通常取決於當前時間。例如,查看每個未完成訂單並檢查是否應發送發票的算法(取決於工作結束後的天數)。在這些情況下,創建發票不是由明確的用戶操作觸發的,而是由後臺作業觸發的。如何在Python中爲所有模塊更改日期/時間?

  • 我可以測試發票創作本身容易
  • 然而,它是很難在一個測試,創建訂單並檢查後臺作業:

    現在,當談到測試這爲我一個問題在正確的時間識別正確的訂單。

到目前爲止,我發現了兩個解決方案:

  • 在測試設置,計算工作日期相對於當前日期。下行:由於沒有明確的日期,代碼變得相當複雜。有時業務邏輯對於邊緣案例來說相當複雜,所以由於所有這些相對日期而變得很難調試。
  • 我有我自己的日期/時間訪問器功能,我用我的代碼。在測試中,我只是設置當前日期,所有模塊都會獲得此日期。因此,我可以在2月模擬訂單創建,並檢查發票是否在4月份很容易創建。下行:第三方模塊不使用這種機制,因此很難集成+測試這些機制。

第二種方法畢竟對我來說比較成功。因此,我正在尋找一種方法來設置Python的日期時間+時間模塊返回的時間。設置日期通常就足夠了,我不需要設置當前的小時或秒(即使這樣很好)。

有沒有這樣的工具?是否有可以使用的(內部)Python API?

回答

8

猴修補time.time可能就足夠了,實際上,因爲它幾乎所有的Python的其他基於時間的程序提供了基礎。這看起來很好地處理了你的用例,而不訴諸更復雜的技巧,並且當你這樣做時並不重要(除了像Queue.py和threading.py那樣的幾個stdlib軟件包,在這種情況下你必須執行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()代碼提供假時間結果。

更新:年後,還有一個替代方案,如Dave Forgac's answer中所述。

+7

你測試過什麼版本的Python試試這個?我剛剛在MacOS 10.6上嘗試了2.6,並且datetime.datetime.now()很幸福地沒有意識到時間的變化。時間 – 2011-02-04 16:42:18

+0

Malcolm,我剛剛在Windows上的Python 2.7.1上再次測試,它的工作原理與上面所示完全相同。也許它是依賴於平臺的,我想這並不令人驚訝。 – 2011-02-05 22:36:59

+4

我使用Python 2.7.3在Ubuntu 12.04上測試了它,但它不起作用。如上所述,在用'mytime'修補'time.time'之後,調用'datetime.datetime.now'仍然返回當前時間。 – djsmith 2012-06-14 16:38:56

0

可能很少有這樣做的方法,例如創建訂單(使用當前時間戳),然後通過某個外部過程(假設數據在數據庫中)直接更改數據庫中的該值。

我會建議別的。你有沒有想過在虛擬機上運行你的應用程序,設置時間來說說2月,創建訂單,然後改變虛擬機的時間?這種方法是最接近真實情況的。

+0

是的,在DB移動日期直接可以工作。但是這有時不容易可行的 - 尤其是你有很多不同的表日期字段(例如每個項目都有時間戳創建,更改+完整的歷史)。因此,改變所有這些可能是一個重大的麻煩。 對於虛擬機:我不會去那裏,因爲這會增加測試的複雜性一些數量級 - >測試片狀更可能,測試運行速度更慢(快速測試對我來說極其重要),它需要一些主要努力將其集成到我的構建服務器上。 – 2010-04-17 11:06:29

1

好一個辦法做到這一點是動態補丁的時間/日期模塊

import time 
import datetime 

class MyDatetime: 

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

datetime.datetime = MyDatetime 

print datetime.datetime().now() 
+1

是的,它可以工作 - 但它仍然太脆弱,因爲我需要對所有外部模塊進行猴子修補:如果外部模塊使用'from datetime import datetime',上面的猴子修補程序不會產生任何影響。 – 2010-04-17 11:07:51

+1

@Felix,如果您在導入其他任何內容之前修補日期時間模塊,它將會產生影響。如果你打算做猴子補丁(這不是一個優雅的解決方案,但可能是務實的),那麼你必須忍受這種尷尬。 – 2010-04-17 17:46:37

2

可以給系統打補丁,通過創建一個自定義DateTime模塊(即使是假的一個 - 請參閱下面的示例)充當代理,然後將其插入到sys.modules字典中。從那裏開始,每個導入日期時間模塊將返回您的代理。
日期時間課程仍然存在警告,特別是當有人這樣做時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是一種非常動態的語言,你可以做很多有趣的事情,但以這種方式修補代碼總是有一定的風險,即使我們在這裏談論測試代碼。但是大多數情況下,我認爲附件功能會使測試修補更加明確,並且就代碼測試的內容而言,您的代碼會更加明確,從而提高可讀性。
因此,如果變化不是太昂貴,我會選擇第二種方法。

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

+2

這些日子似乎是最好的選擇!不幸的是,這個項目只有幾年的時間,但我將在未來使用它:-) – 2017-07-14 08:30:16

+0

事實上,這是迄今爲止最好的解決方案。 – 2018-02-28 15:14:24

相關問題