49

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

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;我不确定这在这种情况下是否会有所不同。)

4

8 回答 8

43

你可以使用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模拟模块调用。

于 2015-05-11T13:36:22.673 回答
31

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

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.

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

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

于 2012-10-25T16:46:01.040 回答
5

我正在使用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。补丁正在替换导入dateSpoofDate,然后您可以重新定义以执行任何您想要的操作。

于 2012-10-25T16:51:15.447 回答
2

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

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

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

于 2013-01-16T14:47:05.287 回答
2

本来问了这个问题...

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

另一种选择是python-libfaketime,它可以比 freezegun 快得多,但在 Windows 上不起作用,而且听起来有点繁琐。

较新的选项是time-machine,在这篇博客文章中介绍了比较这三个选项。

于 2020-07-10T15:15:27.730 回答
2

这是执行此 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

这里的优点是你不需要通过测试模块的本地属性来修补 datetime 模块,它支持调用 unmocked 方法,并且不需要任何外部导入。

于 2022-01-05T18:44:15.677 回答
1

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

请注意,我们正在覆盖仅在我们的类中导入的 datetime 模块

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

import datetime

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

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

于 2019-05-07T16:12:36.790 回答
1

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

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)
于 2020-07-10T11:54:03.960 回答