J'essaie d'écrire des tests pour une classe qui a des méthodes comme:
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
Je veux le tester et pour ce faire, je dois m'assurer que l'appel datetime.datetime.now()
renvoie quelque chose de prévisible.
J'ai lu de nombreux exemples d'utilisation de Mock dans les tests, mais je n'ai rien trouvé de tel que ce dont j'ai besoin, et je ne peux pas trouver comment l'utiliser dans mes tests.
J'ai séparé la méthode get_now()
au cas où il serait plus facile de se moquer de cela, au lieu de datetime.datetime.now()
, mais je suis toujours perplexe. Des réflexions sur la façon d'écrire des UnitTests pour cela en utilisant Mock? (Tout cela est à Django, fwiw; je ne sais pas si cela fait une différence dans ce cas.)
Vous créez une fonction qui renvoie une date/heure spécifique, localisée dans le fuseau horaire transmis:
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.
De cette façon, vous pouvez tester si le datetime sensible au fuseau horaire résultant est correctement géré; les résultats ailleurs devraient montrer le fuseau horaire correct mais auront une date et une heure prévisibles.
Vous utilisez le mocked_get_now
fonctionne comme un effet secondaire lors de la moquerie get_now
; chaque fois que le code appelle get_now
l'appel est enregistré par mock
, etmocked_get_now
est appelé et sa valeur de retour est utilisée comme valeur renvoyée à l'appelant de get_now
.
Vous pouvez utiliser 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)
Il se moque essentiellement des appels de module datetime
.
J'utilise date
, mais la même idée devrait fonctionner pour 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))
Où some.module
importe date
. Le patch remplace le date
importé par SpoofDate
, que vous pouvez ensuite redéfinir pour faire ce que vous voulez.
J'utiliserais les assistants du package 'testfixtures' pour simuler la classe datetime sur laquelle vous appelez now ():
http://packages.python.org/testfixtures/datetime.html#datetimes
De cette façon, vous pouvez tester tous les cas que vous avez, tout le temps.
Utilisation du patch de unittest.mock
from unittest.mock import patch
@patch('MyClass.datetime')
def test_foo(self, mock_datetime):
mock_datetime.datetime.now.return_value = datetime.datetime(2019, 5, 7) #SOME_MOCKED_DATE
Notez que nous remplaçons le module datetime qui est importé uniquement dans notre classe
La classe pour laquelle nous écrivons le test:
import datetime
class MyClass:
def foo():
localtime_now = datetime.datetime.now(timezone)
Nous n'avons pas besoin de la séparer en tant que méthode get_now () juste pour faciliter la simulation.