it-swarm.com.de

Versucht, datetime.date.today () zu verspotten, funktioniert aber nicht

Kann mir jemand sagen, warum das nicht funktioniert?

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

Vielleicht könnte jemand einen besseren Weg vorschlagen?

130

Es gibt einige Probleme.

Erstens ist die Art und Weise, wie Sie mock.patch Verwenden, nicht ganz richtig. Wenn es als Dekorator verwendet wird, ersetzt es die angegebene Funktion/Klasse (in diesem Fall datetime.date.today) Durch ein Mock -Objekt nur innerhalb der dekorierten Funktion . Nur in Ihrer today() wird datetime.date.today Eine andere Funktion sein, die nicht so zu sein scheint, wie Sie es möchten.

Was Sie wirklich wollen, scheint eher so zu sein:

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

Leider funktioniert das nicht:

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

Dies schlägt fehl, weil Python eingebaute Typen unveränderlich sind - siehe diese Antwort für weitere Details.

In diesem Fall würde ich datetime.date selbst unterteilen und die richtige Funktion erstellen:

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

Und jetzt können Sie tun:

>>> datetime.date.today()
NewDate(2010, 1, 1)
105
Daniel G

Eine andere Möglichkeit ist die Verwendung von https://github.com/spulec/freezegun/

Es installieren:

pip install freezegun

Und benutze es:

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

Es wirkt sich auch auf andere datetime-Aufrufe in Methodenaufrufen aus anderen Modulen aus:

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

Und schlussendlich:

$ python main.py
# 2012-01-01
136
Mehdi Behrooz

In den Mock-Dokumenten wird speziell auf datetime.date.today eingegangen, und dies ist möglich, ohne dass eine Dummy-Klasse erstellt werden muss:

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

Ich schätze, ich bin etwas spät dazu gekommen, aber ich denke, das Hauptproblem hier ist, dass Sie datetime.date.today direkt patchen und laut Dokumentation ist dies falsch.

Sie sollten den in die Datei importierten Verweis patchen, in der sich beispielsweise die getestete Funktion befindet.

Angenommen, Sie haben eine functions.py-Datei, in der Sie Folgendes haben:

import datetime

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

dann sollten Sie in Ihrem Test so etwas haben

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

Hoffe das hilft ein bisschen.

32
iferminm

Hinzufügen zu Daniel Gs Lösung :

from datetime import date

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

Dadurch wird eine Klasse erstellt, die beim Instanziieren ein normales datetime.date-Objekt zurückgibt, das jedoch auch geändert werden kann.

@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)
29
eternicode

Vor ein paar Tagen war ich mit der gleichen Situation konfrontiert, und meine Lösung bestand darin, eine Funktion in dem Modul zu definieren, um Folgendes zu testen und nur zu verspotten:

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

Heute habe ich von FreezeGun erfahren, und es scheint diesen Fall sehr schön abzudecken

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

Sie können den folgenden Ansatz verwenden, der auf der Daniel G-Lösung basiert. Dieser hat den Vorteil, dass die Typprüfung mit isinstance(d, datetime.date) nicht unterbrochen wird.

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)

Grundsätzlich ersetzen wir die C-basierte datetime.date - Klasse durch unsere eigene python Unterklasse, die ursprüngliche datetime.date - Instanzen erzeugt und auf isinstance() Abfragen antwortet genau wie native datetime.date.

Verwenden Sie es als Kontext-Manager in Ihren 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)

Ein ähnlicher Ansatz kann verwendet werden, um die Funktion datetime.datetime.now() zu verspotten.

5
Andrey Lebedev

Für mich ist das am einfachsten:

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

VORSICHT für diese Lösung: Alle Funktionen von datetime module von dem target_module hört auf zu arbeiten.

4
frx08

Im Allgemeinen hätten Sie datetime oder vielleicht datetime.date irgendwo in ein Modul importiert. Eine effektivere Möglichkeit zum Verspotten der Methode besteht darin, sie auf dem Modul zu patchen, das sie importiert. Beispiel:

a.py

from datetime import date

def my_method():
    return date.today()

Dann würde für Ihren Test das Scheinobjekt selbst als Argument an die Testmethode übergeben. Sie richten den Mock mit dem gewünschten Ergebniswert ein und rufen dann Ihre getestete Methode auf. Dann würden Sie behaupten, Ihre Methode habe getan, was Sie wollten.

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

Ein Wort der Warnung. Es ist mit Sicherheit möglich, mit Spott über Bord zu gehen. Wenn Sie dies tun, werden Ihre Tests länger, schwerer verständlich und können nicht mehr gewartet werden. Bevor Sie eine Methode verspotten, die so einfach ist wie datetime.date.today, fragen Sie sich, ob Sie wirklich brauchen , um es zu verspotten. Wenn Ihr Test kurz und sachlich ist und gut funktioniert, ohne die Funktion zu verspotten, sehen Sie sich möglicherweise nur ein internes Detail des Codes an, den Sie testen, und nicht ein Objekt, das Sie verspotten müssen.

3
jpmc26

In http://blog.xelnor.net/python-mocking-datetime/ werden mehrere Lösungen beschrieben. Zusammenfassend:

Mock-Objekt - Einfach und effizient, unterbricht jedoch die Prüfung von 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())

Scheinklasse

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)

Benutzen als:

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

Für diejenigen unter Ihnen, die pytest mit mocker verwenden, ist hier, wie ich datetime.datetime.now() verspottet habe, was der ursprünglichen Frage sehr ähnlich ist.

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)

Grundsätzlich muss der Mock gesetzt sein, um das angegebene Datum zurückzugeben. Sie können nicht direkt über das datetime-Objekt patchen.

0
Daniel Butler

Ich habe die @ user3016183-Methode mithilfe eines benutzerdefinierten Dekorators implementiert:

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

Ich dachte, das könnte eines Tages jemandem helfen ...

0
DainDwarf

Ich habe diese Arbeit gemacht, indem ich datetime als realdatetime importiert und die Methoden, die ich im Mock benötigte, durch die realen Methoden ersetzt habe:

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
Adam McKenna

Hier ist eine andere Möglichkeit, datetime.date.today() mit einem zusätzlichen Bonus zu verspotten, dass die restlichen datetime - Funktionen weiterhin funktionieren, da das Verspottungsobjekt so konfiguriert ist, dass es das ursprüngliche datetime - Modul umschließt:

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

Beachten Sie das Argument wraps=datetime Für mock.patch() - Wenn foo_module Andere datetime - Funktionen als date.today() verwendet, werden diese an das Original weitergeleitet umschlossenes datetime Modul.

0
mrts

Vielleicht können Sie Ihre eigene "today ()" - Methode verwenden, die Sie bei Bedarf patchen. Ein Beispiel für das Verspotten von utcnow () finden Sie hier: https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at = default

0

Es ist möglich, Funktionen aus dem datetime -Modul zu verspotten, ohne side_effects Hinzuzufügen.

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
Daniil Mashkin