datetime.date.today ()를 조롱하려고 시도했지만 작동하지 않습니다.


158

왜 이것이 작동하지 않는지 말해 줄 수 있습니까?

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

누군가 더 나은 방법을 제안 할 수 있습니까?



답변:


124

몇 가지 문제가 있습니다.

우선, 당신이 사용하는 방식이 mock.patch옳지 않습니다. 데코레이터로 사용될 때 주어진 함수 / 클래스 (이 경우 datetime.date.today) 는 데코 레이팅 된 함수 내에서만Mock 객체로 대체합니다 . 그래서, 단지 내 것이다 당신이 원하는 것처럼 보이지 않는 다른 기능을합니다.today()datetime.date.today

정말로 원하는 것은 다음과 같습니다.

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

불행히도 이것은 작동하지 않습니다.

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

파이썬 내장 타입은 불변이기 때문에 실패합니다- 자세한 내용 은 이 답변 을 참조하십시오.

이 경우 datetime.date를 직접 서브 클래스하고 올바른 함수를 만듭니다.

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

그리고 지금 당신은 할 수 있습니다 :

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

13
좋은 해결책이지만 불행히도 산세 문제가 발생합니다.
— Baczek

14
이 답변은 훌륭하지만 클래스를 만들지 않고 날짜 시간을 조롱 할 수 있습니다. stackoverflow.com/a/25652721/117268
— Emil Stenström

datetime인스턴스를 원래 값으로 어떻게 복원 하시겠습니까? 와 함께 deepcoppy?
— Oleg Belousov

5
훨씬 쉬운 :patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
— 빅터 가브로

1
더 쉽게 할 수 @patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29))))있습니다.
— Jonhy Beebop

163

또 다른 옵션은 https://github.com/spulec/freezegun/ 을 사용하는 것입니다

설치하십시오 :

pip install freezegun

그리고 그것을 사용하십시오 :

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

또한 다른 모듈의 메소드 호출에서 다른 날짜 및 시간 호출에 영향을줍니다.

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

그리고 마지막으로:

$ python main.py
# 2012-01-01

13
매우 유용한 라이브러리
— Shaun

3
Freezegun 테스트가 느리게 실행되는 경우 python-libfaketime을 시도 할 수도 있습니다 .
— Simon Weber

훌륭한 라이브러리이지만 불행히도 Google App Engine NDB / Datastore에서는 잘 작동하지 않습니다.
— brandones

나는 "freezegun"이 도서관의 이름이라는 것을 좋아합니다. 저는 파이썬 개발자를 정말 좋아합니다! :-D
— MikeyE

작동하지만 freezegun은 느린 것으로 보입니다. 특히 현재 시간 동안 여러 번의 호출로 복잡한 논리가있는 경우 특히 그렇습니다.
— Andrey Belyak

115

가치있는 것을 위해 Mock 문서는 datetime.date.today에 대해 구체적으로 이야기하며 더미 클래스를 만들지 않고도이 작업을 수행 할 수 있습니다.

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
이것은 실제로 작동하지 않았습니다. 항목을 찾는 노력에 감사합니다.
— Pradyot

8
패치 기능에서 "mymodule"이란 무엇입니까?
— seufagner

4
링크를 찾을 여기 "부분 비웃음"아래
— 레오 C 한

3
@seufagner의 인 MyModule은 다소 혼란 방식으로 설명 voidspace.org.uk/python/mock/patch.html#where-to-patch . 모듈이 사용하는 경우 모듈 from datetime import date이름 from datetime import date과 호출이 date.today()나타나는 것처럼 보입니다.
— danio

1
감사. 일했다! 예 : mock.patch ( 'tests.views.datetime')을 mock_date로 사용 : mock_date.today.return_value = datetime.datetime (2016, 9, 18) mock_date.side_effect = lambda * args, ** kw : date (* args , ** kw)
— Latrova

36

나는 이것에 대해 조금 늦게 온 것 같지만 여기서 가장 큰 문제는 datetime.date.today를 직접 패치하고 문서에 따르면 이것이 잘못되었다는 것입니다.

예를 들어 테스트 된 기능이있는 파일로 가져온 참조를 패치해야합니다.

다음과 같은 functions.py 파일이 있다고 가정 해 봅시다.

import datetime

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

그런 다음 테스트에서 다음과 같은 것이 있어야합니다

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

이것이 약간 도움이되기를 바랍니다.


이것은 매우 설득력있게 보이지만 이것을 실행할 수는 없습니다 (을 던졌습니다 NameError: name 'datetime' is not defined). 테스트 파일에서 가져 오지 않은 경우 datetime.strptime참조는 어디 에서 Mock(return_value=...)제공 datetime됩니까? 업데이트 : 괜찮습니다. 방금 datetime테스트 파일에 모듈을 가져 왔습니다 . 나는 트릭이 datetime테스트 파일에서 참조를 숨기는 방법이라고 생각했습니다 .
— imrek 2016 년

@DrunkenMaster 나는 당신이하고있는 것과 당신이 조롱하고있는 참조의 예를보아야 할 것입니다. 당신은 뭘하고 있었 import datetime나 from datetime import strptime? 첫 번째 작업을 수행했다면, 조롱 datetime하고 수행해야 mocked_datetime.strptime.return_value = whatever하며, 나중에 수행해야 합니다. 테스트 된 메소드가있는 파일에서 strptime 참조를 직접 조롱해야합니다.
— iferminm

@israelord 내가 말하고자하는 것은 마지막 코드 스 니펫 (테스트 파일)에 날짜 시간 참조가 Mock(return_value=datetime...)작동 하도록 가져 오기가 누락되었다는 것입니다.
— imrek

32

에 추가하려면 다니엘 G의 솔루션 :

from datetime import date

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

인스턴스화하면 일반 datetime.date 객체를 반환하지만 변경할 수있는 클래스가 만들어집니다.

@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
여기서주의해야합니다. 시작 버전을 사용해야합니다. 그렇지 않으면 datetime.date (또는 datetime 또는 기타)를 사용하면 이상이 생길 수 있습니다. IE-가짜 새 전화 자체에 도달했을 때 스택 깊이에 도달했습니다.
— Danny Staple

가짜 개체가 자체 모듈 인 dpaste.com/790309 에 있으면 문제가 발생하지 않습니다 . 비록 모의 함수와 같은 모듈에 있더라도 가져 오기 date/ datetime자체를 가져 오지는 않지만 전역 적으로 사용 가능한 변수를 사용하므로 아무런 문제가 없습니다 : dpaste.com/790310
— eternicode

덜 간단한 설명은 여기에서 찾을 수 있습니다 : williamjohnbert.com/2011/07/…
— ezdazuzena

9

나는 며칠 전에 같은 상황에 처해 있었고 내 솔루션은 모듈에서 함수를 정의하여 테스트하고 조롱하는 것이 었습니다.

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

오늘 저는 FreezeGun 에 대해 알아 냈습니다 .이 사건을 아름답게 다루는 것 같습니다.

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

가장 쉬운 방법은 다음과 같습니다.

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)

이 솔루션에 대한주의 : 모든 기능 datetime module로부터 target_module중지됩니다 작업.


1
이것은 정말 좋고 간결합니다. 줄 datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)이로 단축 될 수도 있습니다 datetime_mock.now.return_value = datetime(1999, 1, 1). 대신에 패치를 시작하는 start()사용하는 것이 좋습니다 with patch(...):있는지 확인 컨텍스트 관리자를 그 datetime(unmocked) 일반 동작합니다 때 다시 테스트 끝.
— Dirk

내장 라이브러리를 사용하는 솔루션을 항상 선호합니다
— Nam G VU

@ frx08이 조롱을 재설정하는 방법을 알고 있습니까? 나는 datetime.datetime.now()조롱 하는 방법을 의미합니다 ^^?
— Nam G VU

그럼이 모의를 사용하려고 후 -이 솔루션에 대한 하나 개주의의 모든 기능이다 datetime module으로부터 target_module작동이 중지됩니다.
— Nam G VU

1
@ frx08에 동의하면 with ()가 미묘한 영향을 미칩니다. 그 블록 안에 날짜가 모두 있지만 timedelta는 작동을 멈출 것입니다. 우리가 지금 조롱해야하지만 데이트 수학이 계속 진행된다면 어떨까요? 죄송합니다. 전체 datetime 모듈이 아닌 .now ()를 조롱해야합니다.
— Nam G VU

7

Daniel G 솔루션을 기반으로 다음 접근 방식을 사용할 수 있습니다. 이것은로 타입 검사를 중단하지 않는 이점이 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)

기본적으로 C 기반 datetime.date클래스를 자체 Python 하위 클래스로 바꾸면 원래 datetime.date인스턴스 를 생성 하고 isinstance()쿼리를 정확히 native로 응답합니다 datetime.date.

테스트에서 컨텍스트 관리자로 사용하십시오.

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)

datetime.datetime.now()기능 을 조롱하기 위해 비슷한 접근법을 사용할 수 있습니다 .


이것이 파이썬 2.7에서 작동하는지 확실하지 않습니다. 이 __instancecheck__방법 으로 최대 재귀 깊이 RuntimeError가 발생합니다 .
— Dan Loewenherz

이것은 실제로 Python 2.7에서 작동하며 인스턴스 유형 확인 문제를 해결했습니다. 감사합니다!
— Karatheodory

4

일반적으로, 어딘가에 모듈로 가져 왔 datetime거나 아마도 datetime.date가져 왔습니다. 메소드를 모의하는보다 효과적인 방법은 메소드를 가져 오는 모듈에 패치하는 것입니다. 예:

a.py

from datetime import date

def my_method():
    return date.today()

그런 다음 테스트를 위해 모의 객체 자체가 테스트 메소드의 인수로 전달됩니다. 원하는 결과 값으로 모의를 설정 한 다음 테스트중인 메소드를 호출하십시오. 그런 다음 메소드가 원하는 것을 수행했다고 주장합니다.

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

경고의 말. 조롱으로 선외로 나가는 것이 가장 확실합니다. 그렇게하면 테스트가 길어지고 이해하기 어려워지고 유지 관리가 불가능 해집니다. 당신은 간단 같은 방법을 조롱하기 전에 datetime.date.today당신이 정말로 경우 자신에게 물어, 필요 를 조롱. 테스트가 짧고 요점에 도달하고 함수를 조롱하지 않고 제대로 작동하는 경우 조롱해야 할 객체가 아니라 테스트하는 코드의 내부 세부 정보를 볼 수 있습니다.


2

모의 객체가 원래 모듈 을 감싸도록 구성되어 datetime.date.today()있으므로 나머지 datetime기능이 계속 작동 한다는 추가 보너스 로 조롱 하는 또 다른 방법 이 있습니다 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

노트 wraps=datetime에 인수 mock.patch()(가) - foo_module사용 기타 datetime이외의 기능을 date.today()그들이 원래의 포장에 전달됩니다 datetime모듈.


1
정답, 날짜를 모의해야하는 대부분의 테스트는 날짜 시간 모듈을 사용해야합니다.
— Antoine Vo

1

몇 가지 솔루션은 http://blog.xelnor.net/python-mocking-datetime/ 에서 설명합니다. . 요약해서 말하자면:

모의 객체 -간단하고 효율적이지만 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())

모의 수업

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)

로 사용:

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


0

사용자 정의 데코레이터를 사용하여 @ user3016183 메소드를 구현했습니다.

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

언젠가 누군가에게 도움이 될 줄 알았는데 ...


0

datetime추가하지 않고 모듈 에서 함수를 조롱 할 수 있습니다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()

0

mocker와 함께 pytest를 사용하는 사람들을 위해 여기에 내가 조롱 datetime.datetime.now()한 방법 이 있습니다. 원래 질문과 매우 유사합니다.

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)

기본적으로 모의는 지정된 날짜를 반환하도록 설정되어야합니다. 날짜 / 시간 객체를 직접 패치 할 수 없습니다.


0

나는 가져 와서이 일을 만들어 datetime으로 realdatetime내가 진짜 방법과 모의에 필요한 방법을 대체 :

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

datetime이것을 사용하여 조롱 할 수 있습니다 .

모듈에서 sources.py:

import datetime


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

당신의 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')

sources패치 데코레이터에는 무엇이 있습니까?
— 엘레나

친애하는 @elena, 거의 1 년 전에 내가 생각했던 것을 기억하기가 다소 어렵습니다)). 나는 우리의 응용 프로그램 소스의 모든 모듈, 즉 응용 프로그램의 코드를 의미한다고 생각합니다.
— MTMobile

0

CPython은 실제로 pure-Python Lib / datetime.py 및 C- 최적화 된 Modules / _datetimemodule.c를 사용하여 datetime 모듈을 구현합니다 . C 최적화 버전은 패치 할 수 없지만 순수 Python 버전은 패치 할 수 있습니다.

Lib / datetime.py 의 순수 파이썬 구현의 맨 아래에는 다음 코드가 있습니다.

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

이 코드는 모든 C 최적화 정의를 가져오고 모든 순수 파이썬 정의를 효과적으로 대체합니다. CPython이 다음을 수행하여 datetime 모듈의 순수 Python 구현을 사용하도록 강제 할 수 있습니다.

import datetime
import importlib
import sys

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

를 설정함으로써 sys.modules["_datetime"] = None, 파이썬에게 C- 최적화 된 모듈을 무시하도록 지시합니다. 그런 다음 모듈을 다시로드하여 가져옵니다._datetime 가 실패 . 이제 순수 파이썬 정의는 그대로 유지되고 정상적으로 패치 될 수 있습니다.

당신이 사용하는 경우 Pytest를 다음에 위의 코드 조각을 포함 conftest.py 당신은 패치 할 수 있습니다 datetime일반적으로 개체를.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.