【问题标题】:How to mock python's datetime.now() in a class method for unit testing?如何在单元测试的类方法中模拟 python 的 datetime.now()?
【发布时间】:2012-10-15 21:55:41
【问题描述】:

我正在尝试为具有以下方法的类编写测试:

import datetime
import pytz

class MyClass:
    def get_now(self, timezone):
        return datetime.datetime.now(timezone)

    def do_many_things(self, tz_string='Europe/London'):
        tz = pytz.timezone(tz_string)
        localtime_now = self.get_now(tz)
        ...
        return things

我想对其进行测试,为此我需要确保 datetime.datetime.now() 调用返回可预测的内容。

我已经阅读了很多在测试中使用 Mock 的示例,但没有找到完全符合我需要的东西,而且我不知道如何在测试中使用它。

我将get_now() 方法分开,以防它更容易模拟,而不是datetime.datetime.now(),但我仍然很难过。关于如何使用 Mock 为此编写 UnitTests 的任何想法? (这一切都在 Django 中,fwiw;我不确定这在这种情况下是否会有所不同。)

【问题讨论】:

  • 仅供参考,切勿在 datetime 构造函数中使用 pytz 时区。请改用localize
  • 谢谢马克。所以我应该做timezone.localize(datetime.datetime.now())而不是datetime.datetime.now(timezone)?有什么特别好的理由吗?
  • 有时直接分配时区不能正常工作。一个例子见stackoverflow.com/questions/12808845/…
  • 虽然,localize() 只用于幼稚的datetimes。因此,如果now() 是 2012-10-26 15:00:00(没有时区),那么它只是将指定的时区应用于它;它不会转换时间。由于我想获得时区的实际时间,我想我需要这样做:datetime.datetime.now(pytz.utc).astimezone(timezone).

标签: python django unit-testing testing mocking


【解决方案1】:

您将创建一个返回特定日期时间的函数,本地化到传入的时区:

import mock

def mocked_get_now(timezone):
    dt = datetime.datetime(2012, 1, 1, 10, 10, 10)
    return timezone.localize(dt)

@mock.patch('path.to.your.models.MyClass.get_now', side_effect=mocked_get_now)
def your_test(self, mock_obj):
    # Within this test, `MyClass.get_now()` is a mock that'll return a predictable
    # timezone-aware datetime object, set to 2012-01-01 10:10:10.

这样您就可以测试生成的时区感知日期时间是否得到正确处理;其他地方的结果应该显示正确的时区,但会有一个可预测的日期和时间。

您在模拟get_now 时使用mocked_get_now 函数作为副作用;每当代码调用get_now 时,调用被mock 记录,并且 mocked_get_now 被调用,它的返回值用作返回给get_now 调用者的值。

【讨论】:

  • @MartijnPieters 无法导入 mock
  • @AvinashRaj mock 是一个附加包,使用 pip 安装它。在 Python 3 中,它被包含为 unittest.mock
【解决方案2】:

我正在使用date,但同样的想法应该适用于datetime

class SpoofDate(date):
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)

...

from mock import patch

@patch('some.module.date', SpoofDate)
def testSomething(self):
    SpoofDate.today = classmethod(lambda cls : date(2012, 9, 24))

其中some.module 导入date。补丁正在将导入的date 替换为SpoofDate,然后你可以重新定义它来做任何你想做的事情。

【讨论】:

  • @FearlessFuture 你刚刚用datetime 替换了date 吗?你能在datetime写实现吗?
  • @zakiakhmad,下面是我所做的一个例子: class StubDate(datetime.datetime): pass @mock.patch("friend.datetime.datetime", StubDate) def test_generate_date(self): # 使 datetime.datetime.now 返回一个固定值 StubDate.now = classmethod(lambda cls: datetime.datetime(2015, 03, 11, 11, 01)) self.assertEqual( self.friend_obj.generate_date(input), datetime. datetime(2015, 03, 11, 11, 01)) > 块引用
  • @FearlessFuture 非常感谢!为了更好的可读性,我在这里写了要点。 gist.github.com/za/2a217c47582737f88259
【解决方案3】:

我会使用“testfixtures”包中的助手来模拟你现在调用的日期时间类():

http://packages.python.org/testfixtures/datetime.html#datetimes

这样,您就可以一直测试您拥有的所有案例。

【讨论】:

  • 谢谢,比任何其他解决方案都更好、更简单
【解决方案4】:

你可以使用freezegun

from freezegun import freeze_time

def test():
    assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)
    with freeze_time("2012-01-14"):
        assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
    assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)

它基本上模拟了datetime 模块调用。

【讨论】:

  • 谢谢,它有效!但是第一个断言没有,因为月份令牌不被这种格式接受:01datetime.datetime(2012, 1, 14) 有效。
  • freezegun 很慢,尤其是当您通过多次调用 datetime.now() 来测试逻辑时
【解决方案5】:

使用 unittest.mock 的补丁

from unittest.mock import patch

@patch('MyClass.datetime')
def test_foo(self, mock_datetime):
    mock_datetime.datetime.now.return_value = datetime.datetime(2019, 5, 7) #SOME_MOCKED_DATE

请注意,我们正在覆盖仅在我们的类中导入的日期时间模块

我们正在为其编写测试的类:

import datetime

class MyClass:
    def foo():
       localtime_now = datetime.datetime.now(timezone)

我们不必将它作为 get_now() 方法分开,以便更容易模拟。

【讨论】:

    【解决方案6】:

    如果您不想安装任何东西,这是最简单的方法。只需使用, 模拟课 -

    class NewDt(datetime.date):
        @classmethod
         def now(cls):
               return datetime.datetime.strptime('2020-07-10 05:20:20', '%Y-%m-%d %H:%M:%S')
    

    并且在模拟函数之前使用这个补丁

     @mock.patch('module path', NewDt)
    

    【讨论】:

      【解决方案7】:

      原来问过这个问题...

      正如@Jocelyn delalande 所建议的那样,多年来我一直很高兴地使用freezegun

      另一个选项是python-libfaketime,它可以比 freezegun 快得多,但不能在 Windows 上运行,而且听起来有点繁琐。

      较新的选项是time-machine,在this blog post 中引入,用于比较三个选项。

      【讨论】:

        【解决方案8】:

        这是 IMO 最优雅的方式:

        import datetime
        from unittest import mock
        
        test_now = datetime.datetime(1856, 7, 10)
        with mock.patch('datetime.datetime', wraps=datetime.datetime) as dt:
            print(dt.now()) # calls the real thing
            dt.now.return_value = test_now
            print(dt.now()) # calls the mocked value
        

        这里的好处是你不需要通过被测模块的本地属性打补丁日期时间模块,它支持调用非模拟方法,并且不需要任何外部导入。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-11-29
          • 1970-01-01
          • 2014-04-15
          • 1970-01-01
          • 2015-10-21
          • 2015-12-15
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多