2012-10-25 1 views
27

sto cercando di scrivere i test per una classe che ha metodi come:Come prendere in giro python's datetime.now() in un metodo di classe per il test delle unità?

import datetime 
import pytz 

class MyClass: 
    def get_now(self, timezone): 
     return datetime.datetime.now(timezone) 

    def do_many_things(self, tz_string='Europe/London'): 
     tz = pytz.timezone(tz_string) 
     localtime_now = self.get_now(tz) 
     ... 
     return things 

voglio provarlo, e per farlo ho bisogno di fare in modo che la chiamata datetime.datetime.now() restituisce qualcosa di prevedibile.

Ho letto molti esempi di utilizzo di Mock nei test, ma non ho trovato nulla di simile a quello che mi serve, e non riesco a capire come usarlo nei miei test.

Ho separato il metodo get_now() nel caso fosse più facile prenderlo in giro, invece di datetime.datetime.now(), ma sono ancora bloccato. Qualche idea su come scrivere UnitTests per questo usando Mock? (Questo è tutto in Django, FWIW, io non sono sicuro se questo fa la differenza in questo caso.)

+0

Solo per FYI, non utilizzare mai un fuso orario pytz nel costruttore datetime. Usa invece 'localize'. –

+0

Grazie Marco. Quindi invece di 'datetime.datetime.now (timezone)' dovrei fare 'timezone.localize (datetime.datetime.now())'? Qualche ragione particolare è meglio? –

+0

A volte l'assegnazione diretta del fuso orario non funziona correttamente. Per un esempio vedere http://stackoverflow.com/questions/12808845/pytz-america-edmon-offset-wrong –

risposta

20

Faresti crea una funzione che restituisce un datetime specifica, localizzata al fuso orario in passato:

import mock 

def mocked_get_now(timezone): 
    dt = datetime.datetime(2012, 1, 1, 10, 10, 10) 
    return timezone.localize(dt) 

@mock.patch('path.to.your.models.MyClass.get_now', side_effect=mocked_get_now) 
def your_test(self, mock_obj): 
    # Within this test, `MyClass.get_now()` is a mock that'll return a predictable 
    # timezone-aware datetime object, set to 2012-01-01 10:10:10. 

In questo modo è possibile verificare se viene gestito correttamente il datetime timezone-aware risultante; i risultati altrove dovrebbero mostrare il fuso orario corretto ma avranno una data e un'ora prevedibili.

Si utilizza la funzione mocked_get_now come effetto collaterale quando si deride get_now; quando il codice chiama get_now la chiamata viene registrata da mock, emocked_get_now viene chiamato e il suo valore di ritorno viene utilizzato come valore restituito al chiamante di get_now.

+0

Grazie. Come lo chiamerei? Ho appena provato questo e se chiamo 'MyClass.get_now()' allora la versione derisoria viene ignorata, ed è l'originale che viene chiamato. –

+0

@PhilGyford: Scusa, TBH non ho mai usato "finta" prima; una rapida lettura e ho corretto la mia risposta per usare correttamente 'patch'. :-P –

+0

Brillante, sembra funzionare! Due piccole correzioni: 'Mock' dovrebbe essere' mock.Mock' se hai fatto solo 'import mock' (magari aggiungilo all'inizio del tuo esempio?). E se 'mocked_get_now()' non è in una classe, non ha bisogno dell'argomento 'self'; Non riuscivo a capire come farlo funzionare se fosse all'interno di una classe. Grazie mille Martijn! –

4

sto usando date, ma la stessa idea dovrebbe funzionare per datetime:

class SpoofDate(date): 
    def __new__(cls, *args, **kwargs): 
     return date.__new__(date, *args, **kwargs) 

...

from mock import patch 

@patch('some.module.date', SpoofDate) 
def testSomething(self): 
    SpoofDate.today = classmethod(lambda cls : date(2012, 9, 24)) 

Dove some.module importazioni date. Patch sostituisce lo date importato con SpoofDate, che puoi ridefinire per fare quello che vuoi.

+0

Mi ha aiutato! Grazie! – FearlessFuture

+0

@FearlessFuture hai appena sostituito 'date' con' datetime'? Puoi scrivere l'implementazione in 'datetime'. – zakiakhmad

+0

@zakiakhmad, qui di seguito è un esempio di quello che ho fatto: classe StubDate (datetime.datetime): passaggio @ mock.patch ("friend.datetime.datetime", StubDate) def test_generate_date (self): # Make datetime.datetime.now restituisce un valore fisso StubDate.now = classmethod (lambda cls: datetime.datetime (2015, 03, 11, 11, 01)) self.assertEqual ( self.friend_obj.generate_date (input) , datetime.datetime (2015, 03, 11, 11, 01)) > Blockquote – FearlessFuture

24

Si potrebbe utilizzare freezegun:

from freezegun import freeze_time 

def test(): 
    assert datetime.datetime.now() != datetime.datetime(2012, 1, 14) 
    with freeze_time("2012-01-14"): 
     assert datetime.datetime.now() == datetime.datetime(2012, 1, 14) 
    assert datetime.datetime.now() != datetime.datetime(2012, 1, 14) 

Si prende gioco fondamentalmente datetime chiamate moduli.

+0

Esattamente quello di cui avevo bisogno. Grazie per sugegsting – mukesh

+1

Grazie, Funziona! Tuttavia, la prima asserzione non è stata eseguita poiché il token del mese non è stato accettato in questo formato: '01' ma' datetime.datetime (2012, 1, 14) 'funziona. – Montaro

+0

Questa è una risposta molto pulita. Grazie per il suggerimento. – Ahmed