Prova a deridere datetime.date.today (), ma non funziona


158

Qualcuno può dirmi perché questo non funziona?

>>> import mock
>>> @mock.patch('datetime.date.today')
... def today(cls):
...  return date(2010, 1, 1)
...
>>> from datetime import date
>>> date.today()
datetime.date(2010, 12, 19)

Forse qualcuno potrebbe suggerire un modo migliore?



Risposte:


125

Ci sono alcuni problemi

Prima di tutto, il modo in cui stai usando mock.patchnon è del tutto corretto. Se usato come decoratore, sostituisce la data funzione / classe (in questo caso datetime.date.today) con un Mockoggetto solo all'interno della funzione decorata . Quindi, solo all'interno di te today()sarà datetime.date.todayuna funzione diversa, che non sembra essere ciò che desideri.

Quello che vuoi davvero sembra essere più simile a questo:

@mock.patch('datetime.date.today')
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()

Sfortunatamente, questo non funzionerà:

>>> test()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 557, in patched
  File "build/bdist.macosx-10.6-universal/egg/mock.py", line 620, in __enter__
TypeError: can't set attributes of built-in/extension type 'datetime.date'

Questo non riesce perché i tipi predefiniti di Python sono immutabili - vedi questa risposta per maggiori dettagli.

In questo caso, vorrei sottoclassare datetime.date e creare la funzione corretta:

import datetime
class NewDate(datetime.date):
    @classmethod
    def today(cls):
        return cls(2010, 1, 1)
datetime.date = NewDate

E ora potresti fare:

>>> datetime.date.today()
NewDate(2010, 1, 1)

13
una buona soluzione, ma purtroppo causa problemi con il decapaggio.
Baczek,

14
Sebbene questa risposta sia buona, è possibile deridere il datetime senza creare una classe: stackoverflow.com/a/25652721/117268
Emil Stenström

Come ripristineresti l' datetimeistanza al suo valore originale? con deepcoppy?
Oleg Belousov,

5
Molto più facile da fare:patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
Victor Gavro,

1
Più molto più facile da fare @patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29)))).
Jonhy Beebop,

163

Un'altra opzione è quella di utilizzare https://github.com/spulec/freezegun/

Installalo:

pip install freezegun

E usalo:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    from datetime import datetime
    print(datetime.now()) #  2012-01-01 00:00:00

    from datetime import date
    print(date.today()) #  2012-01-01

Influisce anche su altre chiamate datetime in chiamate di metodo da altri moduli:

other_module.py:

from datetime import datetime

def other_method():
    print(datetime.now())    

main.py:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    import other_module
    other_module.other_method()

E infine:

$ python main.py
# 2012-01-01

13
Una biblioteca molto utile
Shaun,

3
Potresti anche provare python-libfaketime se noti che i tuoi test di freezegun vengono eseguiti lentamente.
Simon Weber,

Grande libreria, ma purtroppo non funziona bene con NDB / Datastore di Google App Engine.
Brandones,

Adoro che "freezegun" sia il nome di una biblioteca. Adoro gli sviluppatori Python! :-D
MikeyE

Funziona, ma Freezegun sembra essere lento, specialmente se hai una logica complicata con più chiamate per l'ora corrente.
Andrey Belyak,

115

Per quello che vale, i documenti Mock parlano specificamente di datetime.date.today, ed è possibile farlo senza dover creare una classe fittizia:

https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking

>>> from datetime import date
>>> with patch('mymodule.date') as mock_date:
...     mock_date.today.return_value = date(2010, 10, 8)
...     mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
...
...     assert mymodule.date.today() == date(2010, 10, 8)
...     assert mymodule.date(2009, 6, 8) == date(2009, 6, 8)
...

2
Questo non ha funzionato davvero per me. Anche se apprezzo lo sforzo nel trovare la voce.
Pradyot,

8
cosa significa "mymodule" nella funzione patch?
seufagner,

4
Trovato il link qui sotto "Derisione parziale"
Leo C Han

3
@seufagner mymodule è spiegato in modo piuttosto confuso su voidspace.org.uk/python/mock/patch.html#where-to-patch . Sembra che se il tuo modulo usa, from datetime import dateallora è il nome del modulo dove from datetime import datee date.today()appare la chiamata
danio

1
Grazie. Lavorato! Esempio: con mock.patch ('tests.views.datetime') come mock_date: mock_date.today.return_value = datetime.datetime (2016, 9, 18) mock_date.side_effect = lambda * args, ** kw: date (* args , ** kw)
Latrova,

36

Immagino di essere arrivato un po 'in ritardo per questo, ma penso che il problema principale qui sia che stai correggendo direttamente datetime.date.today e, secondo la documentazione, questo è sbagliato.

Ad esempio, è necessario applicare una patch al riferimento importato nel file in cui si trova la funzione testata.

Supponiamo che tu abbia un file Functions.py in cui hai il seguente:

import datetime

def get_today():
    return datetime.date.today()

quindi, nel tuo test, dovresti avere qualcosa del genere

import datetime
import unittest

from functions import get_today
from mock import patch, Mock

class GetTodayTest(unittest.TestCase):

    @patch('functions.datetime')
    def test_get_today(self, datetime_mock):
        datetime_mock.date.today = Mock(return_value=datetime.strptime('Jun 1 2005', '%b %d %Y'))
        value = get_today()
        # then assert your thing...

Spero che questo aiuti un po '.


Sembra molto avvincente, ma non riesco a farlo funzionare (lancia a NameError: name 'datetime' is not defined). Da dove viene il datetime.strptimeriferimento Mock(return_value=...)se non si importa datetimenel file di test? AGGIORNAMENTO: Va bene, sono appena andato avanti e ho importato il datetimemodulo nel file di test. Ho pensato che il trucco fosse in qualche modo come nascondere il datetimeriferimento dal file di test.
imrek,

@DrunkenMaster Dovrei vedere un esempio di quello che stavi facendo e di quale riferimento stavi deridendo. stavi facendo import datetimeo from datetime import strptime? se stessi facendo il primo, dovresti deridere datetimee fare mocked_datetime.strptime.return_value = whatever, è il successivo, dovresti deridere direttamente il riferimento strptime nel file in cui vive il metodo testato.
iferminm,

@israelord Quello che intendevo dire è che nel tuo ultimo frammento di codice (il file di test) manca un'importazione per il riferimento datetime per far Mock(return_value=datetime...)funzionare.
imrek,

32

Per aggiungere alla soluzione di Daniel G :

from datetime import date

class FakeDate(date):
    "A manipulable date replacement"
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

Questo crea una classe che, quando viene istanziata, restituirà un normale oggetto datetime.date, ma che può anche essere modificata.

@mock.patch('datetime.date', FakeDate)
def test():
    from datetime import date
    FakeDate.today = classmethod(lambda cls: date(2010, 1, 1))
    return date.today()

test() # datetime.date(2010, 1, 1)

2
Fai molta attenzione qui - devi usare la versione from, altrimenti potresti ottenere stranezze se usi datetime.date (o datetime o altri). IE: profondità dello stack raggiunta quando le tue nuove chiamate fasulle stesse.
Danny Staple,

Non avrai questo problema se l'oggetto falso si trova nel suo modulo: dpaste.com/790309 . Anche se, anche se si trova nello stesso modulo della funzione derisa, non importa date/ datetimese stesso, utilizza la variabile disponibile a livello globale, quindi non dovrebbero esserci problemi: dpaste.com/790310
eternicode

una spiegazione meno breve può essere trovata qui: williamjohnbert.com/2011/07/…
ezdazuzena

9

Ho affrontato la stessa situazione un paio di giorni fa e la mia soluzione era quella di definire una funzione nel modulo da testare e solo deridere che:

def get_date_now():
    return datetime.datetime.now()

Oggi ho scoperto FreezeGun , e sembra coprire questo caso magnificamente

from freezegun import freeze_time
import datetime
import unittest


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

9

Il modo più semplice per me è farlo:

import datetime
from unittest.mock import Mock, patch

def test():
    datetime_mock = Mock(wraps=datetime.datetime)
    datetime_mock.now.return_value = datetime.datetime(1999, 1, 1)
    with patch('datetime.datetime', new=datetime_mock):
        assert datetime.datetime.now() == datetime.datetime(1999, 1, 1)

ATTENZIONE per questa soluzione: tutte le funzionalità dal datetime moduledal target_modulelavoro si fermerà.


1
Questo è davvero carino e conciso. La linea datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)potrebbe anche essere abbreviata datetime_mock.now.return_value = datetime(1999, 1, 1). Invece di avviare la patch con start(), considera l'utilizzo del with patch(...):gestore di contesto per assicurarti che datetimesi comporti di nuovo regolarmente (senza blocchi) al termine del test.
Dirk,

Favorire sempre la soluzione che utilizza la libreria integrata
Nam G VU

@ frx08 Posso sapere come resettare questo derisione? Intendo come datetime.datetime.now()smontare ^^?
Nam G VU,

Ebbene dopo aver tentato di usare questa finta - Una cautela per questa soluzione è tutte le funzionalità dal datetime moduledal target_modulesmetterà di funzionare.
Nam G VU,

1
D'accordo @ frx08 il con () avrebbe attenuato il dolore. Sebbene all'interno di quel blocco, ad es. Tutta la data, timedelta smetterà di funzionare. E se avessimo bisogno di essere derisi, ma la matematica della data continua? Siamo spiacenti, dobbiamo aver deriso .now () solo non l'intero modulo datetime.
Nam G VU,

7

È possibile utilizzare il seguente approccio, basato sulla soluzione Daniel G. Questo ha il vantaggio di non interrompere il controllo del tipo con isinstance(d, datetime.date).

import mock

def fixed_today(today):
    from datetime import date

    class FakeDateType(type):
        def __instancecheck__(self, instance):
            return isinstance(instance, date)

    class FakeDate(date):
        __metaclass__ = FakeDateType

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

        @staticmethod
        def today():
            return today

    return mock.patch("datetime.date", FakeDate)

In sostanza, sostituiamo la datetime.dateclasse C-based con la nostra sottoclasse python, che produce datetime.dateistanze originali e risponde alle isinstance()query esattamente come nativadatetime.date .

Usalo come gestore di contesto nei tuoi test:

with fixed_today(datetime.date(2013, 11, 22)):
    # run the code under test
    # note, that these type checks will not break when patch is active:
    assert isinstance(datetime.date.today(), datetime.date)

Un approccio simile può essere usato per simulare la datetime.datetime.now()funzione.


Non sono sicuro che funzioni in Python 2.7. Sto ottenendo una profondità di ricorsione massima RuntimeError con il __instancecheck__metodo.
Dan Loewenherz,

Questo funziona davvero in Python 2.7 e ha risolto il mio problema con il controllo del tipo di istanza, grazie!
Karatheodory,

4

In generale, avresti datetimeo forse datetime.dateimportato in un modulo da qualche parte. Un modo più efficace di deridere il metodo sarebbe quello di patcharlo sul modulo che lo sta importando. Esempio:

a.py

from datetime import date

def my_method():
    return date.today()

Quindi, per il tuo test, l'oggetto simulato stesso verrebbe passato come argomento al metodo test. Impostare il mock con il valore del risultato desiderato e quindi chiamare il metodo sotto test. Quindi affermeresti che il tuo metodo ha fatto quello che volevi.

>>> import mock
>>> import a
>>> @mock.patch('a.date')
... def test_my_method(date_mock):
...     date_mock.today.return_value = mock.sentinel.today
...     result = a.my_method()
...     print result
...     date_mock.today.assert_called_once_with()
...     assert mock.sentinel.today == result
...
>>> test_my_method()
sentinel.today

Un avvertimento. È certamente possibile esagerare con il beffardo. Quando lo fai, rende i test più lunghi, più difficili da capire e impossibili da mantenere. Prima di prendere in giro un metodo semplice comedatetime.date.today , chiediti se hai davvero bisogno di deriderlo. Se il tuo test è breve e preciso e funziona bene senza deridere la funzione, potresti semplicemente guardare un dettaglio interno del codice che stai testando piuttosto che un oggetto che devi prendere in giro.


2

Ecco un altro modo di deridere datetime.date.today()con un ulteriore vantaggio che le altre datetimefunzioni continuano a funzionare, poiché l'oggetto simulato è configurato per avvolgere il datetimemodulo originale :

from unittest import mock, TestCase

import foo_module

class FooTest(TestCase):

    @mock.patch(f'{foo_module.__name__}.datetime', wraps=datetime)
    def test_something(self, mock_datetime):
        # mock only datetime.date.today()
        mock_datetime.date.today.return_value = datetime.date(2019, 3, 15)
        # other calls to datetime functions will be forwarded to original datetime

Nota l' wraps=datetimeargomento a mock.patch()- quando foo_moduleusa altre datetimefunzioni oltre a date.today()queste verranno inoltrate al datetimemodulo wrapped originale .


1
Ottima risposta, la maggior parte dei test in cui è necessario prendere in giro la data che è necessario utilizzare il modulo datetime
Antoine Vo

1

Diverse soluzioni sono discusse in http://blog.xelnor.net/python-mocking-datetime/ . In sintesi:

Oggetto simulato - Semplice ed efficiente ma interrompe i controlli isinstance ():

target = datetime.datetime(2009, 1, 1)
with mock.patch.object(datetime, 'datetime', mock.Mock(wraps=datetime.datetime)) as patched:
    patched.now.return_value = target
    print(datetime.datetime.now())

Classe finta

import datetime
import mock

real_datetime_class = datetime.datetime

def mock_datetime_now(target, dt):
    class DatetimeSubclassMeta(type):
        @classmethod
        def __instancecheck__(mcs, obj):
            return isinstance(obj, real_datetime_class)

    class BaseMockedDatetime(real_datetime_class):
        @classmethod
        def now(cls, tz=None):
            return target.replace(tzinfo=tz)

        @classmethod
        def utcnow(cls):
            return target

    # Python2 & Python3 compatible metaclass
    MockedDatetime = DatetimeSubclassMeta('datetime', (BaseMockedDatetime,), {})

    return mock.patch.object(dt, 'datetime', MockedDatetime)

Usare come:

with mock_datetime_now(target, datetime):
   ....


0

Ho implementato il metodo @ user3016183 usando un decoratore personalizzato:

def changeNow(func, newNow = datetime(2015, 11, 23, 12, 00, 00)):
    """decorator used to change datetime.datetime.now() in the tested function."""
    def retfunc(self):                             
        with mock.patch('mymodule.datetime') as mock_date:                         
            mock_date.now.return_value = newNow
            mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw)
            func(self)
    return retfunc

Ho pensato che potesse aiutare qualcuno un giorno ...


0

È possibile deridere le funzioni dal datetimemodulo senza aggiungereside_effects

import mock
from datetime import datetime
from where_datetime_used import do

initial_date = datetime.strptime('2018-09-27', "%Y-%m-%d")
with mock.patch('where_datetime_used.datetime') as mocked_dt:
    mocked_dt.now.return_value = initial_date
    do()

0

Per quelli di voi che usano Pytest con Mocker ecco come ho deriso datetime.datetime.now()che è molto simile alla domanda originale.

test_get_now(mocker):
    datetime_mock = mocker.patch("blackline_accounts_import.datetime",)
    datetime_mock.datetime.now.return_value=datetime.datetime(2019,3,11,6,2,0,0)

    now == function_being_tested()  # run function

    assert now == datetime.datetime(2019,3,11,6,2,0,0)

In sostanza, il mock deve essere impostato per restituire la data specificata. Non è possibile eseguire la patch direttamente sull'oggetto datetime.


0

Ho fatto questo lavoro importando datetimecome realdatetimee sostituendo i metodi mi servivano nel finto con i metodi reali:

import datetime as realdatetime

@mock.patch('datetime')
def test_method(self, mock_datetime):
    mock_datetime.today = realdatetime.today
    mock_datetime.now.return_value = realdatetime.datetime(2019, 8, 23, 14, 34, 8, 0)

0

Puoi deridere datetime usando questo:

Nel modulo sources.py:

import datetime


class ShowTime:
    def current_date():
        return datetime.date.today().strftime('%Y-%m-%d')

Nel tuo tests.py:

from unittest import TestCase, mock
import datetime


class TestShowTime(TestCase):
    def setUp(self) -> None:
        self.st = sources.ShowTime()
        super().setUp()

    @mock.patch('sources.datetime.date')
    def test_current_date(self, date_mock):
        date_mock.today.return_value = datetime.datetime(year=2019, month=10, day=1)
        current_date = self.st.current_date()
        self.assertEqual(current_date, '2019-10-01')

cosa c'è sourcesnel tuo decoratore di patch?
elena,

Cara @elena, è piuttosto difficile ricordare cosa stavo pensando quasi un anno fa)). Immagino di aver inteso qualsiasi modulo delle nostre fonti di app - solo il codice della tua applicazione.
MTMobile

0

CPython effettivamente implementa il modulo datetime utilizzando sia un puro Python Lib / datetime.py e C-ottimizzato Moduli / _datetimemodule.c . La versione ottimizzata per C non può essere patchata, ma la versione pure-Python può farlo.

Alla base dell'implementazione di pure-Python in Lib / datetime.py c'è questo codice:

try:
    from _datetime import *  # <-- Import from C-optimized module.
except ImportError:
    pass

Questo codice importa tutte le definizioni ottimizzate per C e sostituisce efficacemente tutte le definizioni pure-Python. Possiamo forzare CPython a usare l'implementazione pure-Python del modulo datetime facendo:

import datetime
import importlib
import sys

sys.modules["_datetime"] = None
importlib.reload(datetime)

Impostando sys.modules["_datetime"] = None, diciamo a Python di ignorare il modulo ottimizzato C. Quindi ricarichiamo il modulo che causa l'importazione_datetime fallimento . Ora le definizioni pure-Python rimangono e possono essere patchate normalmente.

Se stai usando Pytest, includi lo snippet sopra in conftest.py e puoi correggere glidatetime oggetti normalmente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.