【问题标题】:How to monkeypatch python's datetime.datetime.now with py.test?如何使用 py.test 对 python 的 datetime.datetime.now 进行猴子补丁?
【发布时间】:2013-12-28 11:53:30
【问题描述】:

我需要测试使用datetime.datetime.now() 的函数。最简单的方法是什么?

【问题讨论】:

    标签: python datetime mocking pytest


    【解决方案1】:

    您需要对 datetime.now 函数进行monkeypatch。在下面的示例中,我正在创建可以稍后在其他测试中重复使用的夹具:

    import datetime
    import pytest
    
    FAKE_TIME = datetime.datetime(2020, 12, 25, 17, 5, 55)
    
    @pytest.fixture
    def patch_datetime_now(monkeypatch):
    
        class mydatetime:
            @classmethod
            def now(cls):
                return FAKE_TIME
    
        monkeypatch.setattr(datetime, 'datetime', mydatetime)
    
    
    def test_patch_datetime(patch_datetime_now):
        assert datetime.datetime.now() == FAKE_TIME
    

    【讨论】:

    • 是否可以只替换now方法?
    • 这行得通,但如果您执行“从日期时间导入日期时间”则不行。详情请看这里:stackoverflow.com/questions/35431476/…
    • 我能够调整这个答案并修补utcnow
    • 你的mydatetime 类应该是datetime.datetime 的子类,否则它将无法执行 datetime.datetime 正常执行的任何其他方法。
    【解决方案2】:

    freezegun module

    from datetime import datetime
    from freezegun import freeze_time # $ pip install freezegun
    
    @freeze_time("Jan 14th, 2012")
    def test_nice_datetime():
        assert datetime.now() == datetime(2012, 1, 14)
    

    freeze_time() 也可以用作上下文管理器。模块支持指定本地时区 UTC 偏移量。

    【讨论】:

      【解决方案3】:

      这是我用于覆盖 now() 但保持其余日期时间工作的夹具(RE:satoru 的问题)。

      它没有经过广泛的测试,但它确实解决了在其他上下文中使用日期时间的问题。对我来说,让 Django ORM 使用这些日期时间值(特别是 isinstance(Freeze.now(), datetime.datetime) == True)非常重要。

      @pytest.fixture
      def freeze(monkeypatch):
          """ Now() manager patches datetime return a fixed, settable, value
              (freezes time)
          """
          import datetime
          original = datetime.datetime
      
          class FreezeMeta(type):
              def __instancecheck__(self, instance):
                  if type(instance) == original or type(instance) == Freeze:
                      return True
      
          class Freeze(datetime.datetime):
              __metaclass__ = FreezeMeta
      
              @classmethod
              def freeze(cls, val):
                  cls.frozen = val
      
              @classmethod
              def now(cls):
                  return cls.frozen
      
              @classmethod
              def delta(cls, timedelta=None, **kwargs):
                  """ Moves time fwd/bwd by the delta"""
                  from datetime import timedelta as td
                  if not timedelta:
                      timedelta = td(**kwargs)
                  cls.frozen += timedelta
      
          monkeypatch.setattr(datetime, 'datetime', Freeze)
          Freeze.freeze(original.now())
          return Freeze
      

      也许题外话了,但可能对其他提出这个问题的人有用。这个夹具允许“冻结”时间,然后在您的测试中随意来回移动它:

      def test_timesensitive(freeze):
          freeze.freeze(2015, 1, 1)
          foo.prepare()  # Uses datetime.now() to prepare its state
          freeze.delta(days=2)
          # Does something that takes in consideration that 2 days have passed
          # i.e. datetime.now() returns a date 2 days in the future
          foo.do_something()
          assert foo.result == expected_result_after_2_days
      

      【讨论】:

        【解决方案4】:

        使用MagicMock(wrap=datetime.datetime) 怎么样?

        此方法模拟datetime.datetime.now(),但其他方法与原始datetime.datetime 相同。

        from unittest.mock import MagicMock
        
        def test_datetime_now(monkeypatch):
            import datetime
            FAKE_NOW = datetime.datetime(2020, 3, 11, 14, 0, 0)
            datetime_mock = MagicMock(wraps=datetime.datetime)
            datetime_mock.now.return_value = FAKE_NOW
            monkeypatch.setattr(datetime, "datetime", datetime_mock)
        
            assert datetime.datetime.now() == FAKE_NOW
        
            # the other methods are available
            assert datetime.datetime.fromisoformat("2020-03-01T00:00:00") == datetime.datetime(2020, 3, 1, 0, 0, 0)
        
        

        这里是使用@pytest.fixture的方法。

        import datetime
        from unittest.mock import MagicMock
        
        import pytest
        
        FAKE_NOW = datetime.datetime(2020, 3, 11, 14, 0, 0)
        
        
        @pytest.fixture()
        def mock_datetime_now(monkeypatch):
            datetime_mock = MagicMock(wraps=datetime.datetime)
            datetime_mock.now.return_value = FAKE_NOW
            monkeypatch.setattr(datetime, "datetime", datetime_mock)
        
        
        def test_datetime_now2(mock_datetime_now):
            assert datetime.datetime.now() == FAKE_NOW
        
            assert datetime.datetime.fromisoformat("2020-03-01T00:00:00") == datetime.datetime(2020, 3, 1, 0, 0, 0)
        
        

        【讨论】:

        • 您忘记导入MagicMock。所以按原样编写代码是行不通的。
        • 感谢您的评论!我修好了。
        • 这将破坏使用isinstance(value, datetime.datetime)的代码,例如,tortoise-orm。
        【解决方案5】:

        改编自其他答案:

        import datetime as dt
        from contextlib import contextmanager
        from unittest.mock import patch
        
        @contextmanager
        def mocked_now(now):
            class MockedDatetime(dt.datetime):
                @classmethod
                def now(cls):
                    return now
        
            with patch("datetime.datetime", MockedDatetime):
                yield
        

        像这样使用:

        def test_now():
            with mocked_now(dt.datetime(2017, 10, 21)):
                assert dt.datetime.now() == dt.datetime(2017, 10, 21)
        

        【讨论】:

        • patch 来自哪里?您错过了导入吗?
        • 你也错过了导入上下文管理器:from contextlib import contextmanager
        猜你喜欢
        • 2023-04-01
        • 2013-04-13
        • 2012-12-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-15
        相关资源
        最近更新 更多