2012-10-25 12 views
27

Próbuję napisać testy dla klasy, która ma metody, takie jak:Jak wyśmiać Pythona datetime.now() w metodzie klasy do testowania jednostkowego?

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 

chcę go przetestować, a do tego muszę się upewnić, że wywołanie datetime.datetime.now() zwraca coś przewidywalnego.

Czytałem dużo przykładów używania testów w Mock, ale nie znalazłem niczego tak bardzo, jak potrzebuję i nie mogę się dowiedzieć, jak z nich korzystać w moich testach.

Wyodrębniłem metodę get_now() na wypadek, gdyby łatwiej było to udawać, zamiast datetime.datetime.now(), ale nadal jestem zakłopotany. Wszelkie przemyślenia na temat pisania UnitTests do tego przy użyciu Mock? (To wszystko w Django, FWIW; nie jestem pewien, czy to robi różnicę w tym przypadku).

+0

Po prostu dla FYI, nigdy nie używaj strefy czasowej pytz w konstruktorze datetime. Zamiast tego użyj 'localize'. –

+0

Dzięki Mark. Więc zamiast 'datetime.datetime.now (timezone)' powinienem zrobić 'timezone.localize (datetime.datetime.now())'? Czy jakiś szczególny powód jest lepszy? –

+0

Czasami przypisanie strefy czasowej bezpośrednio nie działa poprawnie. Na przykład zobacz http://stackoverflow.com/questions/12808845/pytz-america-edmon-offset-wrong –

Odpowiedz

20

Można by utworzyć funkcję, która zwraca konkretny datetime, zlokalizowaną w strefie czasowej przyjętej w:

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. 

W ten sposób można przetestować, czy wynikowa datetime świadoma czasu jest poprawnie obsługiwana; Wyniki w innym miejscu powinny pokazywać właściwą strefę czasową, ale będą miały przewidywalną datę i godzinę.

Używana jest funkcja mocked_get_now jako efekt uboczny przy kpieniu z get_now; zawsze, gdy kod wywołuje get_now, połączenie jest rejestrowane przez mock, imocked_get_now jest wywoływane, a jego wartość zwracana jest używana jako wartość zwracana do wywołującego z get_now.

+0

Dzięki. Jak mógłbym to nazwać? Właśnie próbowałem tego i jeśli zadzwonię do 'MyClass.get_now()', wtedy wyśmiewana wersja zostanie zignorowana, a to zostanie wywołany oryginał. –

+0

@PhilGyford: Przepraszam, TBH Nigdy wcześniej nie używałam 'mock'; szybki odczyt i poprawiłem swoją odpowiedź, aby poprawnie używać 'łatki'. :-P –

+0

Genialny, który wydaje się działać! Dwie małe poprawki: 'Mock' powinien brzmieć' mock.Mock', jeśli zrobiłeś tylko 'import mock' (może dodać to na początku twojego przykładu?). A jeśli 'mocked_get_now()' nie znajduje się w klasie, to nie potrzebuje argumentu 'self'; Nie mogłem się dowiedzieć, jak to działa, jeśli było w klasie. Bardzo dziękuję Martijn! –

4

Używam date, ale sama idea powinna pracować dla 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)) 

Gdzie some.module import date. Patch zastępuje zaimportowaną wersję date za pomocą SpoofDate, którą możesz następnie zmienić na nowo, aby robić, co chcesz.

+0

Pomógł mi! Dzięki! – FearlessFuture

+0

@FearlessFuture właśnie zastąpiłeś 'date' z' datetime'? Czy możesz napisać implementację w 'datetime'. – zakiakhmad

+0

@zakiakhmad, poniżej jest przykładem tego, co zrobiłem: klasa StubDate (datetime.datetime): wprost @ mock.patch ("friend.datetime.datetime" StubDate) def test_generate_date (self): # Make datetime.datetime.now zwraca stałą wartość StubDate.now = classmethod (lambda cls: datetime.datetime (2015, 03, 11, 11, 01)) self.assertEqual ( self.friend_obj.generate_date (wejście) , datetime.datetime (2015, 03, 11, 11, 01)) > Blockquote – FearlessFuture

24

Można użyć 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) 

Zasadniczo drwi datetime połączeń modułu.

+0

Dokładnie to, czego potrzebowałem. Dzięki za sugegsting – mukesh

+1

Dzięki, działa! Jednak pierwszy asercja nie spowodował, że token miesiąca nie został zaakceptowany w tym formacie: "01", ale "datetime.datetime (2012, 1, 14)" działa. – Montaro

+0

To jest bardzo czysta odpowiedź. Dzieki za sugestie. – Ahmed

Powiązane problemy