Datetime.date.today () ile alay etmeye çalışıyor, ancak çalışmıyor


159

Biri bana bunun neden çalışmadığını söyleyebilir mi?

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

Belki biri daha iyi bir yol önerebilir?



Yanıtlar:


125

Birkaç sorun var.

Her şeyden önce, kullanma şekliniz mock.patchdoğru değil. Dekoratör olarak kullanıldığında, verilen işlevi / sınıfı (bu durumda datetime.date.today) yalnızca dekore edilmiş işlev içindeki bir Mocknesneyle değiştirir . Yani, sadece içinde senin olacak ne istediğinizi olmak görünmüyor, farklı bir fonksiyon olmak.today()datetime.date.today

Gerçekten istediğiniz şey daha çok gibi görünüyor:

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

Ne yazık ki, bu işe yaramaz:

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

Python yerleşik türleri değiştirilemez olduğu için bu başarısız olur - daha fazla ayrıntı için bu cevaba bakın.

Bu durumda, ben datetime.date kendimi alt sınıf ve doğru işlevi oluşturmak:

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

Ve şimdi şunları yapabilirsiniz:

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

13
güzel bir çözüm, ancak maalesef asitleme ile ilgili sorunlara neden oluyor.
Baczek

14
Bu cevap iyi olsa da, bir sınıf oluşturmadan datetime alay etmek mümkündür: stackoverflow.com/a/25652721/117268
Emil Stenström

Örneği datetimeorijinal değerine nasıl geri yüklersiniz ? ile deepcoppy?
Oleg Belousov

5
patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
Yapması

1
Yapması çok daha kolay @patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29)))).
Jonhy Beebop

163

Başka bir seçenek https://github.com/spulec/freezegun/ kullanmaktır

Yükle:

pip install freezegun

Ve kullanın:

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

Ayrıca, diğer modüllerden yöntem çağrılarındaki diğer datetime çağrılarını da etkiler:

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

Ve sonunda:

$ python main.py
# 2012-01-01

13
Çok kullanışlı bir kütüphane
Shaun

3
Freezegun testlerinizin yavaş çalıştığını fark ederseniz python-libfaketime'yi de deneyebilirsiniz .
Simon Weber

Harika bir kütüphane, ancak maalesef Google App Engine NDB / Datastore ile iyi oynamıyor.
brandones

"Freezegun" un bir kütüphanenin adı olduğunu seviyorum. Python geliştiricilerini gerçekten çok seviyorum! :-D
MikeyE

Çalışıyor, ancak freezegun yavaş görünüyor, özellikle de şu an için birden fazla çağrı ile karmaşık mantığınız varsa.
Andrey Belyak

115

Değer için, Mock belgeleri datetime.date.today hakkında özellikle konuşur ve bunu bir kukla sınıf oluşturmak zorunda kalmadan yapmak mümkündür:

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
Bu benim için gerçekten işe yaramadı. Gerçi girişi bulma çabasını takdir ediyorum.
Pradyot

8
yama işlevinde "mymodule" ne demektir?
seufagner

4
Bağlantıyı Bulunan burada "Kısmi alay" altında
Leo C Han

3
@seufagner mymodule voidspace.org.uk/python/mock/patch.html#where-to-patch adresinde oldukça kafa karıştırıcı bir şekilde açıklanmıştır . Görünüşe göre modülünüz kullanıyorsa from datetime import date, modülün adı from datetime import dateve aramanın date.today()göründüğü
addır

1
Teşekkürler. Yaradı! Örnek: mock_date olarak mock.patch ('tests.views.datetime') ile: mock_date.today.return_value = datetime.datetime (2016, 9, 18) mock_date.side_effect = lambda * args, ** kw: date (* args , ** kw)
Latrova

36

Sanırım bunun için biraz geç geldim ama burada asıl sorun datetime.date.today'a doğrudan yama uyguladığınız ve belgelere göre bu yanlış olduğunu düşünüyorum.

Örneğin, test edilen işlevin bulunduğu dosyaya içe aktarılan referansı yamalamanız gerekir.

Diyelim ki aşağıdakilere sahip bir function.py dosyanız var:

import datetime

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

testinizde böyle bir şeye sahip olmalısınız

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

Umarım bu biraz yardımcı olur.


Bu çok cazip görünüyor, ama bunu çalıştırmak için alamıyorum (atar NameError: name 'datetime' is not defined). Test dosyanıza içe aktarmıyorsanız datetime.strptimebaşvuru nereden Mock(return_value=...)geliyor datetime? GÜNCELLEME: Sorun değil, devam ettim ve datetimemodülü test dosyasına aktardım. Ben hile bazı datetimetest dosyasındaki referans gizleme olduğunu düşündüm .
imrek

@DrunkenMaster Ne yaptığınıza ve alay etmekte olduğunuz referansa bir örnek görmek zorundayım. yapıyordun import datetimeyoksa from datetime import strptime? ilkini yapıyorsanız, alay etmelisiniz datetimeve yapmalısınız mocked_datetime.strptime.return_value = whatever, daha sonra olanı, test edilen yöntemin yaşadığı dosyadaki strptime referansını doğrudan alay etmelisiniz.
iferminm

@israelord Söylemek istediğim son kod snippet'iniz (test dosyası) datetime referans Mock(return_value=datetime...)iş yapmak için bir ithalat eksik olmasıdır .
imrek

32

Daniel G'nin çözümüne eklemek için :

from datetime import date

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

Bu, somutlaştırıldığında normal datetime.date nesnesini döndürecek, ancak değiştirilebilen bir sınıf oluşturur.

@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
Burada çok dikkatli olun. IE - Sahte yeniniz kendisini çağırdığında yığın derinliğine ulaşıldı.
Danny Staple

Sahte nesne kendi modülündeyse bu sorunla karşılaşmazsınız: dpaste.com/790309 . Yine de, alaycı işlevle aynı modülde olsa bile, içe date/ datetimekendisini almaz , global olarak kullanılabilir değişkeni kullanır, bu nedenle sorun olmamalıdır: dpaste.com/790310
eternicode

daha az kısa bir açıklama burada bulunabilir: williamjohnbert.com/2011/07/…
ezdazuzena

9

Birkaç gün önce aynı durumla karşı karşıya kaldım ve çözümüm, test etmek ve sadece alay etmek için modüldeki bir işlevi tanımlamaktı:

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

Bugün FreezeGun'u öğrendim ve bu davayı güzel bir şekilde ele alıyor gibi görünüyor

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

Benim için en kolay yol bunu yapmak:

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)

Bu çözüm için DİKKAT: Tüm işlevsellik datetime modulegelen target_moduledurduracak çalışma.


1
Bu gerçekten hoş ve özlü. datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)Hatta hat kısaltılabilir datetime_mock.now.return_value = datetime(1999, 1, 1). Düzeltme ekini ile başlatmak yerine, testiniz sona erdiğinde tekrar düzenli (engelsiz) davranmasını sağlamak start()için with patch(...):bağlam yöneticisini kullanmayı düşünün datetime.
Dirk


@ frx08 Bu alaycılığı nasıl sıfırlayabilirim? Yani nasıl datetime.datetime.now()engellenir ^ ^ almak için ?
Nam G VU

Eh bu alay kullanmaya çalışıyorum sonra - bu çözüm için bir DİKKAT tüm işlevselliği datetime modulegelen target_moduleçalışmayı durdurur.
Nam G VU

1
@ Frx08 ile birlikte () acıyı hafifletir. Bu bloğun içinde tüm tarih gibi olsa da, timedelta çalışmayı durduracaktır. Ya şimdi alay edersek ama tarih matematik hala devam ederse? Maalesef, .now () yalnızca datetime modülünün tamamını alay etmemeliydik.
Nam G VU

8

Daniel G çözümüne dayalı olarak aşağıdaki yaklaşımı kullanabilirsiniz. Bu, tip kontrolünü kırmama avantajına sahiptir 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)

Temel olarak, C tabanlı datetime.datesınıfı, özgün datetime.dateörnekler üreten ve isinstance()sorgulara tam olarak yerel olarak yanıt veren kendi python alt sınıfımızla değiştiriyoruz datetime.date.

Testlerinizde içerik yöneticisi olarak kullanın:

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)

Benzer yaklaşım, datetime.datetime.now()işlev alay etmek için kullanılabilir .


Bunun Python 2.7'de çalıştığından emin değilim. Yöntem ile RuntimeError maksimum özyineleme derinliği alıyorum __instancecheck__.
Dan Loewenherz

Bu gerçekten Python 2.7'de çalışıyor ve örnek türü denetimi ile sorunumu çözdü, teşekkürler!
Karatheodory

4

Genel anlamda, olurdu datetimeya da belki datetime.datebir modül yerde ithal. Yöntemin atılmasının daha etkili bir yolu, onu içe aktarılan modüle yapıştırmak olacaktır. Misal:

a.py

from datetime import date

def my_method():
    return date.today()

Daha sonra testiniz için, sahte nesnenin kendisi test yöntemine argüman olarak geçirilir. Sahte, istediğiniz sonuç değeri ile ayarlamak ve sonra test yönteminizi çağırmak. O zaman yönteminizin istediğinizi yaptığını iddia edersiniz.

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

Uyarı kelimesi. Kesinlikle alay ile denize girmek mümkündür. Bunu yaptığınızda, testlerinizi daha uzun, anlaşılması daha zor ve bakımı imkansız hale getirir. Ne kadar basit bir yöntemle alay etmeden önce datetime.date.today, gerçekten alay etmeniz gerekip gerekmediğini kendinize sorun . Testiniz kısa ve noktaya geldiyse ve işlev alay etmeden iyi çalışıyorsa, taklit etmeniz gereken bir nesne yerine test ettiğiniz kodun dahili bir ayrıntısına bakıyor olabilirsiniz.


2

Sahte nesne orijinal modülü sarmak için yapılandırıldığından, işlevlerin datetime.date.today()geri kalanının datetimeçalışmaya devam ettiği ek bir bonusla alay etmenin başka bir yolu datetime:

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

- dışında diğer işlevleri kullandığında , orijinal sarılmış modüle iletilecek wraps=datetimeargümanı not edin .mock.patch()foo_moduledatetimedate.today()datetime


1
Harika bir cevap, tarih alay etmeniz gereken çoğu test datetime modülünü kullanmanız gerekecek
Antoine Vo

1

Çeşitli çözümler http://blog.xelnor.net/python-mocking-datetime/ adresinde tartışılmaktadır . Özetle:

Sahte nesne - Basit ve verimli ancak isinstance () kontrollerini keser:

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

Sahte sınıf

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)

Kullanım şekli:

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


0

Özel bir dekoratör kullanarak @ user3016183 yöntemini uyguladım:

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

Bunun bir gün birine yardım edebileceğini düşündüm ...


0

Eklemeksizin datetimemodülden fonksiyonları taklit etmek mümkündürside_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

Mocker ile pytest kullananlarınız için burada datetime.datetime.now()orijinal soruya çok benzeyen alay ettim .

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)

Temelde alay belirtilen tarihi geri döndürecek şekilde ayarlanmalıdır. Doğrudan datetime nesnesini yamalayamazsınız.


0

Ben ithal ederek bu işi yapılmış datetimeolarak realdatetimeve gerçek yöntemlerle taklidinin gerekli yöntemleri yerine:

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

Bunu kullanarak alay edebilirsiniz datetime:

Modülde sources.py:

import datetime


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

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

sourcesyama dekoratörde ne var ?
elena

Sevgili @elena, neredeyse bir yıl önce ne düşündüğümü hatırlamak oldukça zor)). Uygulama kaynaklarımızın sadece herhangi bir modülünü kastediyorum - sadece uygulamanızın kodu.
MTMobile

0

CPython aslında saf-Python Lib / datetime.py ve C-optimize Modüller / _datetimemodule.c'yi kullanarak datetime modülünü uygular . C optimizasyonlu sürüm yamalanamaz ancak salt Python sürümü kullanılabilir.

Lib / datetime.py dosyasındaki pure-Python uygulamasının altında şu kod yer alır:

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

Bu kod, C için optimize edilmiş tüm tanımları alır ve tüm pure-Python tanımlarının yerini alır. CPython'u datetime modülünün pure-Python uygulamasını kullanmaya zorlayabiliriz:

import datetime
import importlib
import sys

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

Ayarlayarak sys.modules["_datetime"] = NonePython'a C optimizasyon modülünü yoksaymasını söyleriz. Daha sonra içe aktarma _datetimeişleminin başarısız olmasına neden olan modülü yeniden yüklüyoruz. Şimdi saf Python tanımları kalır ve normal olarak yamalanabilir.

Pytest kullanıyorsanız yukarıdaki snippet'i conftest.py dosyasına ekleyin ve datetimenesneleri normal şekilde yatırabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.