Đang cố gắng giả định datetime.date.today (), nhưng không hoạt động


158

Bất cứ ai có thể cho tôi biết tại sao điều này không làm việc?

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

Có lẽ ai đó có thể đề xuất một cách tốt hơn?



Câu trả lời:


124

Có một vài vấn đề.

Trước hết, cách bạn đang sử dụng mock.patchkhông hoàn toàn đúng. Khi được sử dụng như một trình trang trí, nó thay thế hàm / lớp đã cho (trong trường hợp này datetime.date.today) bằng một Mockđối tượng chỉ trong hàm được trang trí . Vì vậy, chỉ trong phạm vi của bạn today()sẽ datetime.date.todaylà một chức năng khác, dường như không phải là những gì bạn muốn.

Những gì bạn thực sự muốn dường như giống như thế này:

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

Thật không may, điều này sẽ không hoạt động:

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

Điều này không thành công vì các loại tích hợp Python là bất biến - xem câu trả lời này để biết thêm chi tiết.

Trong trường hợp này, tôi sẽ tự phân lớp datetime.date và tạo hàm đúng:

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

Và bây giờ bạn có thể làm:

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

13
một giải pháp tốt đẹp, nhưng không may gây ra vấn đề với dưa chua.
Baczek

14
Mặc dù câu trả lời này là tốt, nhưng có thể giả định datetime mà không cần tạo một lớp: stackoverflow.com/a/25652721/117268
Emil Stenström

Làm thế nào bạn sẽ khôi phục lại datetimeví dụ về giá trị ban đầu? với deepcoppy?
Oleg Belousov

5
Dễ dàng hơn nhiều để làm:patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
Victor Gavro

1
Dễ dàng hơn nhiều để làm @patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29)))).
Jonhy Beebop

162

Một tùy chọn khác là sử dụng https://github.com/spulec/freezegun/

Cài đặt nó:

pip install freezegun

Và sử dụng 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

Nó cũng ảnh hưởng đến các cuộc gọi datetime khác trong các cuộc gọi phương thức từ các mô-đun khác:

other_module.py:

from datetime import datetime

def other_method():
    print(datetime.now())    

chính:

from freezegun import freeze_time

@freeze_time("2012-01-01")
def test_something():

    import other_module
    other_module.other_method()

Và cuối cùng:

$ python main.py
# 2012-01-01

13
Một thư viện rất hữu ích
Shaun

3
Bạn cũng có thể thử python-libfaketime nếu bạn nhận thấy các bài kiểm tra freezegun của mình chạy chậm.
Simon Weber

Thư viện tuyệt vời, nhưng không may là không chơi tốt với Google App Engine NDB / Datastore.
brandones

Tôi thích "freezegun" là tên của một thư viện. Tôi thực sự yêu các nhà phát triển Python! :-D
MikeyE

Hoạt động, nhưng freezegun dường như chậm, đặc biệt nếu bạn có logic phức tạp với nhiều cuộc gọi cho thời gian hiện tại.
Andrey Belyak

115

Để biết giá trị của nó, các tài liệu Mock nói về datetime.date.today một cách cụ thể và có thể làm điều này mà không cần phải tạo một lớp giả:

https://docs.python.org/3/l 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
Điều này đã không thực sự làm việc cho tôi. Mặc dù tôi đánh giá cao những nỗ lực trong việc xác định vị trí mục.
Pradyot

8
"mymodule" ở chức năng vá là gì?
seufagner

4
Tìm thấy liên kết ở đây trong "Chế độ một phần"
Leo C Han

3
@seufagner mymodule được giải thích theo cách khá khó hiểu tại voidspace.org.uk/python/mock/patch.html#where-to-patch . Có vẻ như nếu mô-đun của bạn sử dụng from datetime import datethì đó là tên của mô-đun from datetime import datevà cuộc gọi sẽ date.today()xuất hiện
danio

1
Cảm ơn. Đã làm việc! Ví dụ: với mock.patch ('tests.view.datetime') là mock_date: mock_date.today.return_value = datetime.datetime (2016, 9, 18) mock_date.side_effect = lambda * args, ** kw: date , ** kw)
Latrova

36

Tôi đoán rằng tôi đã đến muộn một chút vì điều này nhưng tôi nghĩ vấn đề chính ở đây là bạn đang vá trực tiếp datetime.date.today và, theo tài liệu, điều này là sai.

Ví dụ, bạn nên vá tham chiếu được nhập trong tệp có chức năng được kiểm tra.

Giả sử bạn có một tệp tin chức năng, nơi bạn có các mục sau:

import datetime

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

sau đó, trong bài kiểm tra của bạn, bạn nên có một cái gì đó như thế này

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

Mong rằng điều này giúp được chút ít.


Điều này có vẻ rất hấp dẫn, nhưng tôi không thể chạy nó (ném a NameError: name 'datetime' is not defined). Trường hợp không datetime.strptimetham chiếu trong Mock(return_value=...)đến từ nếu bạn không nhập khẩu datetimetrong tập tin thử nghiệm của bạn? CẬP NHẬT: Không sao, tôi chỉ tiếp tục và nhập datetimemô-đun vào tệp thử nghiệm. Tôi nghĩ rằng mẹo là một số cách mà bạn đang ẩn datetimetài liệu tham khảo từ tệp thử nghiệm.
imrek

@DrunkenMaster Tôi phải xem một ví dụ về những gì bạn đang làm và tham chiếu nào bạn đang chế giễu. bạn đã làm import datetimehay from datetime import strptime? nếu bạn đang thực hiện cái đầu tiên, bạn sẽ phải chế giễu datetimevà làm mocked_datetime.strptime.return_value = whatever, là cái sau, bạn phải trực tiếp chế giễu tham chiếu strptime trong tệp nơi phương thức được thử nghiệm tồn tại.
iferminm

@israelord Điều tôi muốn nói là đoạn mã cuối cùng của bạn (tệp kiểm tra) bị thiếu một lần nhập cho tham chiếu datetime để thực hiện Mock(return_value=datetime...)công việc.
imrek

32

Để thêm vào giải pháp của Daniel G :

from datetime import date

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

Điều này tạo ra một lớp, khi được khởi tạo, sẽ trả về một đối tượng datetime.date bình thường, nhưng cũng có thể được thay đổi.

@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
Hãy cẩn thận ở đây - bạn phải sử dụng phiên bản từ, nếu không bạn có thể gặp sự lạ nếu bạn sử dụng datetime.date (hoặc datetime hoặc những người khác). IE - độ sâu ngăn xếp đạt được khi bản thân bạn mới gọi.
Daniel Staple

Bạn sẽ không gặp vấn đề đó nếu đối tượng giả mạo nằm trong mô-đun của chính nó: dpaste.com/790309 . Mặc dù, ngay cả khi nó nằm trong cùng một mô-đun với chức năng bị chế giễu, nó không nhập date/ datetimechính nó, nó sử dụng biến có sẵn trên toàn cầu, do đó, không có vấn đề gì: dpaste.com/790310
evericode

một lời giải thích ngắn gọn hơn có thể được tìm thấy ở đây: williamjohnbert.com/2011/07/
Khăn

9

Tôi đã đối mặt với tình huống tương tự một vài ngày trước, và giải pháp của tôi là xác định một chức năng trong mô-đun để kiểm tra và chỉ chế giễu rằng:

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

Hôm nay tôi đã tìm hiểu về FreezeGun , và nó dường như bao quát trường hợp này rất hay

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

Cách dễ nhất đối với tôi là làm điều này:

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)

THẬN TRỌNG cho giải pháp này: tất cả các chức năng từ datetime moduletừ target_modulesẽ ngừng làm việc.


1
Điều này thực sự tốt đẹp và súc tích. Dòng datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)thậm chí có thể được rút ngắn datetime_mock.now.return_value = datetime(1999, 1, 1). Thay vì bắt đầu bản vá với start(), hãy xem xét sử dụng trình with patch(...):quản lý bối cảnh để đảm bảo rằng nó datetimehoạt động thường xuyên (không bị khóa) khi thử nghiệm của bạn kết thúc.
Dirk

Luôn ưu tiên giải pháp sử dụng thư viện tích hợp
Nam G VU

@ frx08 Tôi có thể biết cách thiết lập lại bản giả này không? Tôi có nghĩa là làm thế nào để có được datetime.datetime.now()unmocked ^^?
Nam G VU

Chà, sau khi thử sử dụng giả này - một THẬN TRỌNG cho giải pháp này là tất cả chức năng từ datetime module từ target_modulesẽ ngừng làm việc.
Nam G VU

1
Đồng ý @ frx08 với with () sẽ làm giảm nỗi đau. Mặc dù bên trong khối đó tất cả, ví dụ như ngày, timedelta sẽ ngừng hoạt động. Điều gì sẽ xảy ra nếu chúng ta cần chế giễu nhưng ngày toán vẫn tiếp tục? Xin lỗi, chúng ta phải có .now () bị nhạo không phải toàn bộ mô-đun datetime.
Nam G VU

7

Bạn có thể sử dụng phương pháp sau, dựa trên giải pháp Daniel G. Điều này có lợi thế là không vi phạm kiểm tra kiểu với 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)

Về cơ bản, chúng tôi thay thế datetime.datelớp dựa trên C bằng lớp con python của riêng chúng tôi, tạo ra các thể hiện ban đầu datetime.datevà trả lời isinstance()các truy vấn chính xác như bản địadatetime.date .

Sử dụng nó như trình quản lý bối cảnh trong các thử nghiệm của bạ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)

Cách tiếp cận tương tự có thể được sử dụng để datetime.datetime.now()chức năng giả .


Tôi không chắc điều này hoạt động trong Python 2.7. Tôi đang nhận được độ sâu đệ quy tối đa RuntimeError với __instancecheck__phương thức.
Dan Loewenherz

Điều này thực sự hoạt động trong Python 2.7 và nó đã giải quyết vấn đề của tôi với kiểm tra loại cá thể, cảm ơn!
Kareditodory

4

Nói chung, bạn sẽ có datetimehoặc có lẽdatetime.date nhập vào một mô-đun ở đâu đó. Một cách hiệu quả hơn để chế tạo phương pháp sẽ là vá nó trên mô-đun đang nhập nó. Thí dụ:

a.py

from datetime import date

def my_method():
    return date.today()

Sau đó, đối với thử nghiệm của bạn, chính đối tượng giả sẽ được truyền làm đối số cho phương thức thử nghiệm. Bạn sẽ thiết lập giả với giá trị kết quả bạn muốn và sau đó gọi phương thức của bạn đang được thử nghiệm. Sau đó, bạn sẽ khẳng định rằng phương pháp của bạn đã làm những gì bạn muốn.

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

Một lời cảnh báo. Chắc chắn là có thể đi quá mức với chế nhạo. Khi bạn làm điều đó, nó làm cho các bài kiểm tra của bạn dài hơn, khó hiểu hơn và không thể duy trì. Trước khi bạn chế nhạo một phương pháp đơn giản như datetime.date.today, hãy tự hỏi mình xem bạn có thực sự cần phải chế giễu nó không. Nếu bài kiểm tra của bạn ngắn và đến điểm và hoạt động tốt mà không cần chế giễu chức năng, bạn có thể chỉ đang xem một chi tiết nội bộ của mã bạn đang kiểm tra chứ không phải là một đối tượng bạn cần giả.


2

Đây là một cách khác để giả định datetime.date.today()với phần thưởng bổ sung mà các datetimechức năng còn lại tiếp tục hoạt động, vì đối tượng giả được cấu hình để bọc datetimemô-đun gốc :

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

Lưu ý wraps=datetimeđối số mock.patch()- khi foo_modulesử dụng các datetimechức năng khác ngoài date.today()chúng sẽ được chuyển tiếp đến datetimemô-đun được bao bọc ban đầu .


1
Câu trả lời tuyệt vời, hầu hết các bài kiểm tra mà bạn cần mô phỏng ngày bạn sẽ cần sử dụng mô-đun datetime
Antoine Vo

1

Một số giải pháp được thảo luận trong http://blog.xelnor.net/python-mocking-datetime/ . Tóm tắt:

Đối tượng giả - Đơn giản và hiệu quả nhưng phá vỡ kiểm tra 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())

Lớp giả

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)

Sử dụng như là:

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


0

Tôi đã triển khai phương thức @ user3016183 bằng cách sử dụng trang trí tùy chỉnh:

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

Tôi nghĩ rằng một ngày nào đó sẽ giúp được ai đó ...


0

Có thể mô phỏng các chức năng từ datetimemô-đun mà không cần thêmside_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

Đối với những bạn sử dụng pytest với mocker ở đây là cách tôi chế giễu datetime.datetime.now()rất giống với câu hỏi ban đầu.

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)

Về cơ bản, giả phải được đặt để trả về ngày đã chỉ định. Bạn không thể vá trực tiếp đối tượng của datetime.


0

Tôi đã thực hiện công việc này bằng cách nhập datetimedưới dạng realdatetimevà thay thế các phương thức tôi cần trong bản giả bằng các phương thức thực:

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

Bạn có thể chế giễu datetime bằng cách này:

Trong mô-đun sources.py:

import datetime


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

Trong của bạn 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')

những gì sourcestrong trang trí vá của bạn?
elena

Gửi @elena, thật khó để nhớ những gì tôi đã nghĩ về gần một năm trước)). Tôi đoán tôi có nghĩa là bất kỳ mô-đun nào trong các nguồn ứng dụng của chúng tôi - chỉ là mã ứng dụng của bạn.
MTMobile

0

CPython thực sự triển khai mô đun datetime bằng cả Python thuần Lib / datetime.pyMô-đun được tối ưu hóa C / _datetimemodule.c . Phiên bản tối ưu hóa C không thể được vá nhưng phiên bản Python thuần có thể.

Ở dưới cùng của triển khai Python thuần Lib / datetime.py là mã này:

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

Mã này nhập tất cả các định nghĩa được tối ưu hóa C và thay thế hiệu quả tất cả các định nghĩa Python thuần túy. Chúng ta có thể buộc CPython sử dụng triển khai Python thuần túy của mô đun datetime bằng cách thực hiện:

import datetime
import importlib
import sys

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

Bằng cách cài đặt sys.modules["_datetime"] = None, chúng tôi bảo Python bỏ qua mô-đun được tối ưu hóa C. Sau đó, chúng tôi tải lại mô-đun gây ra việc nhập từ_datetime không thành công. Bây giờ các định nghĩa Python thuần túy vẫn còn và có thể được vá bình thường.

Nếu bạn đang sử dụng Pytest thì hãy bao gồm đoạn trích ở trên trong conftest.py và bạn có thể vá datetimecác đối tượng bình thường.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.