web-dev-qa-db-fra.com

Comment se moquer de datetime.now () de python dans une méthode de classe pour les tests unitaires?

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.)

38
Phil Gyford

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.

25
Martijn Pieters

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.

37

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))

some.module importe date. Le patch remplace le date importé par SpoofDate, que vous pouvez ensuite redéfinir pour faire ce que vous voulez.

4
Izkata

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.

2
Chris Withers

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.

0
Riya John