【问题标题】:Trying to mock datetime.date.today(), but not working试图模拟 datetime.date.today(),但不工作
【发布时间】:2011-05-27 18:52:53
【问题描述】:

谁能告诉我为什么这不起作用?

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

也许有人可以提出更好的方法?

【问题讨论】:

标签: python testing datetime mocking


【解决方案1】:

另一种选择是使用 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())    

ma​​in.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

【讨论】:

  • 一个非常好用的库
  • 如果您发现 freezegun 测试运行缓慢,也可以尝试 python-libfaketime
  • 很棒的库,但遗憾的是不能很好地与 Google App Engine NDB/Datastore 配合使用。
  • 我喜欢“freezegun”是图书馆的名字。我真的很喜欢 Python 开发者! :-D
  • 工作,但 freezegun 似乎很慢,特别是如果您有复杂的逻辑并多次调用当前时间。
【解决方案2】:

对于它的价值,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)
...

【讨论】:

  • 这对我来说真的不起作用。虽然我很欣赏找到条目的努力。
  • 补丁功能中的“mymodule”是什么意思?
  • 在“部分模拟”下找到链接here
  • @seufagner mymodule 在voidspace.org.uk/python/mock/patch.html#where-to-patch 以一种相当混乱的方式进行了解释。似乎如果您的模块使用from datetime import date,那么它就是出现from datetime import date 和对date.today() 的调用的模块的名称
  • 谢谢。工作!示例:使用 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)
【解决方案3】:

有一些问题。

首先,您使用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'

这会失败,因为 Python 内置类型是不可变的 - 请参阅 this answer 了解更多详细信息。

在这种情况下,我将自己继承 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)

【讨论】:

  • 一个不错的解决方案,但不幸的是会导致酸洗问题。
  • 虽然这个答案很好,但可以在不创建类的情况下模拟日期时间:stackoverflow.com/a/25652721/117268
  • 如何将datetime 实例恢复为原始值? deepcoppy ?
  • 更容易做到:patch('mymodule.datetime', Mock(today=lambda: date(2017, 11, 29)))
  • 更容易做到@patch('module_you_want_to_test.date', Mock( today=Mock(return_value=datetime.date(2017, 11, 29)))).
【解决方案4】:

我想我来晚了一点,但我认为这里的主要问题是您正在直接修补 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)。如果您没有在测试文件中导入datetimeMock(return_value=...) 中的datetime.strptime 引用来自哪里?更新:没关系,我只是继续在测试文件中导入了datetime 模块。我认为诀窍在于您如何从测试文件中隐藏 datetime 引用。
  • @DrunkenMaster 我必须看看你在做什么以及你在嘲笑哪个参考的例子。你在做import datetime 还是from datetime import strptime?如果您正在执行第一个,则必须模拟 datetime 并执行 mocked_datetime.strptime.return_value = whatever,后者是后者,您必须直接模拟测试方法所在文件中的 strptime 引用。
  • @israelord 我的意思是您的最后一个代码 sn-p(测试文件)缺少日期时间引用的导入以使 Mock(return_value=datetime...) 工作。
  • 但是,如果 OP 只是想模拟 today 方法而其余部分保持不变,这将不起作用。
【解决方案5】:

添加到Daniel G's solution

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)

【讨论】:

  • 这里要非常小心 - 你必须使用 from 版本,否则如果你使用 datetime.date (或 datetime 或其他)你可能会感到奇怪。 IE - 当你的假 new 调用自身时达到堆栈深度。
  • 如果假对象在它自己的模块中,你就不会遇到这个问题:dpaste.com/790309。虽然,即使它与模拟函数在同一个模块中,它也不会导入 date/datetime 本身,它使用全局可用变量,所以应该没有问题:dpaste.com/790310
  • 可以在这里找到不太简短的解释:williamjohnbert.com/2011/07/…
【解决方案6】:

这是模拟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

注意mock.patch()wraps=datetime 参数– 当foo_module 使用除date.today() 之外的其他datetime 函数时,它们将被转发到原始包装的datetime 模块。

【讨论】:

  • 很好的答案,大多数需要模拟日期的测试都需要使用 datetime 模块
  • 这应该是公认的答案。多亏了这一点,我现在知道patch(wraps=) 似乎没有出现在官方文档中。如前所述,此解决方案还允许您保留模块其余部分的全部功能。
  • 感谢您的赏金,@EliadL!
  • 为我工作。注意那些不知道的人(我是一个),这里的补丁夹具(如果这是正确的术语)必须是测试的第一个参数(在self之后)
  • @EliadL 它在文档中,有点:docs.python.org/3/library/unittest.mock.html#patch ...并查看该段落的底部:"patch() 需要任意关键字参数。如果修补的对象是异步的,这些将传递给 AsyncMock,否则传递给 MagicMock,如果指定,则传递给 new_callable。".
【解决方案7】:

对我来说最简单的方法是这样做:

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 moduletarget_module 的所有功能都将停止工作。

【讨论】:

  • 这真的很简洁。 datetime_mock.now = Mock(return_value=datetime(1999, 1, 1) 行甚至可以缩短为 datetime_mock.now.return_value = datetime(1999, 1, 1)。不要使用start() 启动补丁,而是考虑使用with patch(...): 上下文管理器来确保datetime 在您的测试结束时再次表现正常(未模拟)。
  • 一直支持使用内置库的解决方案
  • @frx08 我可以知道如何重置这个模拟吗?我的意思是如何让datetime.datetime.now()不被嘲笑^^?
  • 在尝试使用这个模拟之后——这个解决方案的一个注意事项是来自datetime module 的所有功能来自target_module 将停止工作。
  • 同意@frx08 with() 会减轻痛苦。尽管在那个块内,例如日期,timedelta 将停止工作。如果我们现在需要模拟但日期数学仍在继续怎么办?抱歉,我们必须只模拟 .now() 而不是整个 datetime 模块。
【解决方案8】:

几天前我遇到了同样的情况,我的解决方案是在模块中定义一个函数来测试并模拟它:

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

    您可以使用以下方法,基于 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() 查询与原生 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()函数。

    【讨论】:

    • 我不确定这在 Python 2.7 中是否有效。我使用 __instancecheck__ 方法获得了最大递归深度 RuntimeError。
    • 这确实适用于 Python 2.7,它解决了我的实例类型检查问题,谢谢!
    【解决方案10】:

    一般来说,您会将datetimedatetime.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 这样简单的方法之前,问问自己是否真的需要模拟它。如果您的测试很简短,而且在没有模拟函数的情况下可以正常工作,那么您可能只是查看正在测试的代码的内部细节,而不是需要模拟的对象。

    【讨论】:

    • 我已经建立在你的想法之上,但我正在修补 my_method : date_mock.return_value = datetime.strptime('28 May 2130', '%d %b %Y')。它就像一个魅力。谢谢
    【解决方案11】:

    CPython 实际上使用纯 Python Lib/datetime.py 和 C 优化的 Modules/_datetimemodule.c 来实现 datetime 模块。 C 优化版本不能打补丁,但纯 Python 版本可以。

    Lib/datetime.py 中纯 Python 实现的底部是这段代码:

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

    此代码导入所有 C 优化定义并有效替换所有纯 Python 定义。我们可以强制 CPython 使用 datetime 模块的纯 Python 实现:

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

    通过设置sys.modules["_datetime"] = None,我们告诉 Python 忽略 C 优化模块。然后我们重新加载导致从_datetime 导入失败的模块。现在纯 Python 定义仍然存在,可以正常修补。

    如果您使用的是Pytest,则在conftest.py 中包含上面的sn-p,您可以正常修补datetime 对象。

    【讨论】:

    • 这个答案太棒了!谢谢
    【解决方案12】:

    我们可以使用 pytest-mock (https://pypi.org/project/pytest-mock/) 模拟对象来模拟特定模块中的日期时间行为

    假设您想在以下文件中模拟日期时间

    # File path - source_dir/x/a.py
    import datetime
    
    def name_function():
         name = datetime.now()
         return f"name_{name}"
    

    在测试函数中,测试运行时会在函数中添加mocker

    def test_name_function(mocker):
         mocker.patch('x.a.datetime')
         x.a.datetime.now.return_value = datetime(2019, 1, 1)
    
         actual = name_function()
    
         assert actual == "name_2019-01-01"
    

    【讨论】:

    • 这是迄今为止最好的答案,并且很容易适应常规的mockunittest.mock(因为pytest-mock 只是一个语法包装器)。修补 C 模块本身,然后使用一系列 Mock 对象来获取您需要的函数或属性的修补程序。
    【解决方案13】:

    对我来说最好的方法是结合@Daniel G 和@frx08 解决方案:

    class Test_mock_date:
        class NewDate(datetime.datetime):
            @classmethod
            def now(cls, tz=None):
                return cls(2021, 5, 12)
    
        def test_mock_date(self):
            with patch('datetime.datetime', new = self.NewDate):
                assert datetime.datetime.now() == datetime.datetime(2021, 5, 12, 0, 0)
    

    你可以看看下面我写的中篇文章,里面有不同的例子,关于如何使用 MagicMock https://medium.com/@camposer/d2113513b365

    【讨论】:

      【解决方案14】:

      对于那些使用 pytest 和 pytest-mock 的人(最后有关 pytest-mock 的更多信息)这里是我如何模拟 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)
      

      基本上必须将模拟设置为返回指定的日期。您无法直接修补 datetime 的对象。

      Pytest-mock 是一个使模拟对象成为固定装置的库。更多详情可查看here

      【讨论】:

      • 我相信 mocker 来自 pytest-mock。提供一个链接/解释一下可能会很好。
      • 好点!我总是用 pytest 安装它,所以我从不单独考虑它们。
      • 谢谢。我是这些 Python 模拟、补丁等的新手,所以我认为我有足够的能力尝试理解模块 unittest、pytest 和 pytest-qt,然后再尝试有一天了解对更多工具的需求。请注意,查看该页面,我看到它提供了“间谍”。在 Java/Groovy/Spock 领域中,我经常使用 Spy。虽然通常经验丰富的 TDD 专家似乎说您可能永远不需要间谍,但我一直发现它们非常有用。
      • @mikerodent 完全明白。我花了一段时间才想尝试使用它们。 Pytest-mock 只是围绕 unitest 的模拟和补丁功能的一个薄包装。一旦你下来 unittest mock pytest-mock 删除一些样板代码 - 没什么特别的。在开始处理模拟时,我发现这非常有用realpython.com/python-mock-library
      【解决方案15】:

      http://blog.xelnor.net/python-mocking-datetime/ 中讨论了几种解决方案。总结:

      Mock 对象 - 简单高效但会破坏 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):
         ....
      

      【讨论】:

        【解决方案16】:

        也许您可以使用自己的“today()”方法,在需要的地方进行修补。可以在此处找到模拟 utcnow() 的示例:https://bitbucket.org/k_bx/blog/src/tip/source/en_posts/2012-07-13-double-call-hack.rst?at=default

        【讨论】:

        【解决方案17】:

        我使用自定义装饰器实现了@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
        

        我认为有一天这可能会对某人有所帮助......

        【讨论】:

          【解决方案18】:

          可以在不添加side_effects 的情况下模拟来自datetime 模块的函数

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

          【讨论】:

            【解决方案19】:

            我通过将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)
            

            【讨论】:

              【解决方案20】:

              你可以使用这个模拟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,很难记住我大约一年前在想什么))。我想我的意思是我们应用程序源的任何模块 - 只是您的应用程序的代码。
              【解决方案21】:

              对于那些在测试类中使用修补程序的人,以下是我成功修补日期时间功能的方法:

              from datetime import datetime
              import unittest
              from unittest.mock import Mock, patch
              
              # Replace with the proper path to the module you would
              # like datetime to be mocked
              from path.to.my_module
              
              class MyTestCases(unittest.TestCase):
              
                  def setUp(self):
                      """execute on class instantiation"""
                      # Record both times at the same moment
                      self.dt_now, self.dt_utcnow = datetime.now(), datetime.utcnow()
              
                      # After retrieving real (or hardcoded datetime values), 
                      # proceed to mock them in desired module
                      self.patch_datetime_functions()
              
              
                  def patch_datetime_functions(self) -> None:
                      """
                      Patch datetime.now() and datetime.utcnow() to prevent issues when
                      comparing expected dates
                      """
              
                      # Create a patcher
                      self.patcher_dt = patch(
                          'path.to.my_module'
                      )
              
                      # Start but make sure cleanup always occurs
                      self.patcher_dt.start()
                      self.addCleanup(self.patcher_dt.stop)
              
                      # Perform the actual patch – use lambdas as mock functions
                      datetime_mock = Mock(wraps=datetime)
                      datetime_mock.now.return_value = self.dt_now
                      datetime_mock.utcnow.return_value = self.dt_utcnow
              
                      my_module.datetime = datetime_mock
              
              
                  # Here's what it will look like when testing:
                  def some_test(self):
                      curr_dt = self.dt_now
                      returned_dt = my_module.datetime.utcnow()
                      
                      # Compare the dates
                      self.assertEqual(curr_dt, returned_dt,
                          'Datetime values should be equal'
                      )
              
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2013-03-13
                • 1970-01-01
                • 2019-04-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-06-04
                • 1970-01-01
                相关资源
                最近更新 更多