Quelqu'un peut-il me dire pourquoi cela ne fonctionne pas?
>>> 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)
Peut-être que quelqu'un pourrait suggérer un meilleur moyen?
Il y a quelques problèmes.
Tout d'abord, la façon dont vous utilisez mock.patch
N'est pas tout à fait correcte. Utilisé comme décorateur, il remplace la fonction/classe donnée (dans ce cas, datetime.date.today
) Par un objet Mock
uniquement dans la fonction décorée . Donc, seulement dans votre today()
, datetime.date.today
Sera une fonction différente, ce qui ne semble pas être ce que vous voulez.
Ce que vous voulez vraiment semble plus ressembler à ceci:
@mock.patch('datetime.date.today')
def test():
datetime.date.today.return_value = date(2010, 1, 1)
print datetime.date.today()
Malheureusement, cela ne fonctionnera pas:
>>> 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'
Cela échoue car Python sont immuables - voir cette réponse pour plus de détails.
Dans ce cas, je sous-classerais datetime.date et créerais la fonction appropriée:
import datetime
class NewDate(datetime.date):
@classmethod
def today(cls):
return cls(2010, 1, 1)
datetime.date = NewDate
Et maintenant tu pourrais faire:
>>> datetime.date.today()
NewDate(2010, 1, 1)
Une autre option consiste à utiliser https://github.com/spulec/freezegun/
Installez-le:
pip install freezegun
Et utilisez-le:
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
Cela affecte également les autres appels datetime dans les appels de méthode d'autres modules:
autre_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()
Et enfin:
$ python main.py
# 2012-01-01
Pour ce qui en vaut la peine, les documents Mock parlent spécifiquement de datetime.date.today, et il est possible de le faire sans avoir à créer une classe fictive:
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)
...
Je suppose que je suis arrivé un peu en retard pour cela, mais je pense que le principal problème ici est que vous corrigez directement datetime.date.today et que, d’après la documentation, cela est faux.
Vous devez corriger la référence importée dans le fichier contenant la fonction testée, par exemple.
Disons que vous avez un fichier functions.py où vous avez les éléments suivants:
import datetime
def get_today():
return datetime.date.today()
alors, dans votre test, vous devriez avoir quelque chose comme ça
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...
J'espère que cela aide un peu.
Pour ajouter à solution de Daniel G :
from datetime import date
class FakeDate(date):
"A manipulable date replacement"
def __new__(cls, *args, **kwargs):
return date.__new__(date, *args, **kwargs)
Cela crée une classe qui, lorsqu'elle est instanciée, retournera un objet datetime.date normal, mais qui peut également être modifiée.
@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)
J'ai fait face à la même situation il y a quelques jours et ma solution consistait à définir une fonction dans le module à tester et à simuler celle-ci:
def get_date_now():
return datetime.datetime.now()
Aujourd'hui, j'ai découvert FreezeGun , et cela semble couvrir joliment ce cas.
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)
Vous pouvez utiliser l'approche suivante, basée sur la solution de Daniel G. Celui-ci a l'avantage de ne pas interrompre la vérification de type avec 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)
Fondamentalement, nous remplaçons la classe datetime.date
Basée sur C par notre propre sous-classe python), qui produit des instances datetime.date
Originales et répond aux requêtes isinstance()
exactement comme natif datetime.date
.
Utilisez-le comme gestionnaire de contexte dans vos tests:
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)
Une approche similaire peut être utilisée pour se moquer de la fonction datetime.datetime.now()
.
Le moyen le plus simple pour moi est de faire ceci:
from unittest import patch, Mock
def test():
datetime_mock = Mock(wraps=datetime)
datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)
patch('target_module.datetime', new=datetime_mock).start()
ATTENTION pour cette solution: toutes les fonctionnalités de datetime module
du target_module
cessera de fonctionner.
De manière générale, vous auriez datetime
ou peut-être datetime.date
importé quelque part dans un module. Un moyen plus efficace de se moquer de la méthode serait de le patcher sur le module qui l'importe. Exemple:
a.py
from datetime import date
def my_method():
return date.today()
Ensuite, pour votre test, l'objet simulé lui-même serait transmis en tant qu'argument à la méthode de test. Vous pouvez configurer la maquette avec la valeur de résultat souhaitée, puis appeler votre méthode sous test. Ensuite, vous affirmeriez que votre méthode a fait ce que vous voulez.
>>> 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 mot d'avertissement. Il est certainement possible d'aller à la mer avec des moqueries. Lorsque vous le faites, vos tests sont plus longs, plus difficiles à comprendre et impossibles à maintenir. Avant de vous moquer d'une méthode aussi simple que datetime.date.today
, demandez-vous si vous avez vraiment besoin de vous moquer de lui. Si votre test est court et précis et fonctionne correctement sans se moquer de la fonction, vous pouvez simplement regarder un détail interne du code que vous testez plutôt qu'un objet que vous devez simuler.
Plusieurs solutions sont discutées dans http://blog.xelnor.net/python-mocking-datetime/ . En résumé:
Objet fictif - Simple et efficace mais rompt les vérifications 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())
Mock class
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)
Utilisé comme:
with mock_datetime_now(target, datetime):
....
Pour ceux qui utilisent pytest with mocker, voici comment je me suis moqué de datetime.datetime.now()
, qui est très similaire à la question initiale.
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)
La maquette doit essentiellement être configurée pour renvoyer la date spécifiée. Vous ne pouvez pas appliquer directement sur l'objet datetime.
J'ai implémenté la méthode @ user3016183 à l'aide d'un décorateur personnalisé:
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
Je pensais que cela pourrait aider quelqu'un un jour ...
J'ai réalisé ce travail en important datetime
en tant que realdatetime
et en remplaçant les méthodes dont j'avais besoin dans la maquette par les méthodes réelles:
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)
Voici un autre moyen de simuler datetime.date.today()
avec un bonus supplémentaire: le reste des fonctions datetime
continue de fonctionner, car l'objet simulé est configuré pour envelopper le module datetime
d'origine:
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
Notez l'argument wraps=datetime
À mock.patch()
- lorsque foo_module
Utilise d'autres fonctions datetime
à côté de date.today()
, elles seront transmises à l'original. enveloppé datetime
module.
Vous pourriez peut-être utiliser votre propre méthode "today ()" que vous corrigerez si nécessaire. Exemple avec moqueur utcnow () peut être trouvé ici: https://bitbucket.org/k_bx/blog/src/tip/source/fr_posts/2012-07-13-double-call-hack.rst?at = par défaut
Il est possible de simuler des fonctions à partir du module datetime
sans ajouter side_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()