试图模拟datetime.date.today(),但不能正常工作

Bel*_*dez 137 python testing datetime mocking

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

>>> 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)
Run Code Online (Sandbox Code Playgroud)

也许有人可以建议一个更好的方法?

小智 142

另一种选择是使用 https://github.com/spulec/freezegun/

安装它:

pip install freezegun
Run Code Online (Sandbox Code Playgroud)

并使用它:

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
Run Code Online (Sandbox Code Playgroud)

它还影响来自其他模块的方法调用中的其他日期时间调用:

other_module.py:

from datetime import datetime

def other_method():
    print(datetime.now())    
Run Code Online (Sandbox Code Playgroud)

main.py:

from freezegun import freeze_time

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

    import other_module
    other_module.other_method()
Run Code Online (Sandbox Code Playgroud)

最后:

$ python main.py
# 2012-01-01
Run Code Online (Sandbox Code Playgroud)

  • 一个非常有用的库 (11认同)
  • 如果您注意到您的freezegun测试运行缓慢,您也可以尝试[python-libfaketime](https://github.com/simon-weber/python-libfaketime). (3认同)

Dan*_*l G 110

有一些问题.

首先,你使用的mock.patch方式并不完全正确.当用作装饰器时,它仅在装饰函数内datetime.date.todayMock对象替换给定的函数/类(在本例中).所以,只有在你的意志中才会有不同的功能,这似乎不是你想要的.today()datetime.date.today

你真正想要的似乎更像是这样的:

@mock.patch('datetime.date.today')
def test():
    datetime.date.today.return_value = date(2010, 1, 1)
    print datetime.date.today()
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不起作用:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

这是因为Python内置类型是不可变的 - 请参阅此答案以获取更多详细信息.

在这种情况下,我会自己将datetime.date子类化并创建正确的函数:

import datetime
class NewDate(datetime.date):
    @classmethod
    def today(cls):
        return cls(2010, 1, 1)
datetime.date = NewDate
Run Code Online (Sandbox Code Playgroud)

现在你可以这样做:

>>> datetime.date.today()
NewDate(2010, 1, 1)
Run Code Online (Sandbox Code Playgroud)

  • 一个很好的解决方案,但不幸的是导致酸洗问题. (13认同)
  • 虽然这个答案很好,但可以在不创建类的情况下模拟日期时间:http://stackoverflow.com/a/25652721/117268 (10认同)
  • 更容易做:`patch('mymodule.datetime',Mock(今天= lambda:date(2017,11,29))) (4认同)
  • 更容易做到`@patch('module_you_want_to_test.date', Mock( Today=Mock(return_value=datetime.date(2017, 11, 29))))`。 (2认同)

kpu*_*pup 97

值得一提的是,模拟文档专门讨论了datetime.date.today,并且可以在不创建虚拟类的情况下执行此操作:

http://www.voidspace.org.uk/python/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)
...
Run Code Online (Sandbox Code Playgroud)

  • 补丁功能的"mymodule"是什么意思? (7认同)
  • 在"Partial Mocking"下找到链接[here](http://www.voidspace.org.uk/python/mock/examples.html) (3认同)
  • @seufagner mymodule 在 http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch 中以一种相当混乱的方式进行了解释。似乎如果您的模块使用`from datetime import date` 那么它就是模块的名称,其中`from datetime import date` 和对`date.today()` 的调用出现 (3认同)
  • 这对我来说并不适用.虽然我很欣赏找到这个条目的努力. (2认同)

ife*_*inm 33

我想我来晚了一点,但我认为这里的主要问题是你直接修补datetime.date.today,根据文档,这是错误的.

例如,您应该修补在测试函数所在的文件中导入的引用.

假设您有一个functions.py文件,其中包含以下内容:

import datetime

def get_today():
    return datetime.date.today()
Run Code Online (Sandbox Code Playgroud)

那么,在你的测试中,你应该有这样的东西

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...
Run Code Online (Sandbox Code Playgroud)

希望这有点帮助.


ete*_*ode 30

要添加到Daniel G的解决方案中:

from datetime import date

class FakeDate(date):
    "A manipulable date replacement"
    def __new__(cls, *args, **kwargs):
        return date.__new__(date, *args, **kwargs)
Run Code Online (Sandbox Code Playgroud)

这将创建一个类,在实例化时,它将返回一个普通的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)
Run Code Online (Sandbox Code Playgroud)

  • 在这里要非常小心 - 你必须使用from版本,否则你可能会因为使用datetime.date(或datetime或其他)而变得怪异.IE - 当您的假新呼叫本身时达到堆栈深度. (2认同)

mrt*_*rts 11

这是模拟的另一种方法,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
Run Code Online (Sandbox Code Playgroud)

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

  • 这应该是公认的答案。感谢这个,我现在知道了“patch(wraps=)”,它似乎没有出现在官方文档中。如前所述,此解决方案还允许您保留模块其余部分的全部功能。 (6认同)
  • 很好的答案,大多数需要模拟日期的测试都需要使用日期时间模块 (2认同)

And*_*dev 6

您可以使用以下方法,基于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)
Run Code Online (Sandbox Code Playgroud)

基本上,我们datetime.date用我们自己的python子类替换基于C的类,它生成原始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)
Run Code Online (Sandbox Code Playgroud)

类似的方法可用于模拟datetime.datetime.now()函数.


Hit*_*kun 6

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

def get_date_now():
    return datetime.datetime.now()
Run Code Online (Sandbox Code Playgroud)

今天我发现有关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)
Run Code Online (Sandbox Code Playgroud)


Gra*_*ntJ 6

CPython 实际上使用纯 Python Lib/datetime.py和 C-optimized Modules/_datetimemodule.c来实现 datetime 模块。C 优化版本无法修补,但纯 Python 版本可以。

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

try:
    from _datetime import *  # <-- Import from C-optimized module.
except ImportError:
    pass
Run Code Online (Sandbox Code Playgroud)

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

import datetime
import importlib
import sys

sys.modules["_datetime"] = None
importlib.reload(datetime)
Run Code Online (Sandbox Code Playgroud)

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

如果您使用Pytest,则将上面的代码片段包含在conftest.py中,您就可以正常修补datetime对象。


小智 6

我们可以使用 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}"
Run Code Online (Sandbox Code Playgroud)

在测试函数中,测试运行时会在函数中添加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"
Run Code Online (Sandbox Code Playgroud)

  • 这是迄今为止最好的答案,并且很容易适应常规的“mock”或“unittest.mock”(因为“pytest-mock”只是一个语法包装器)。修补 C 模块本身,然后使用一系列 Mock 对象来获取函数或属性所需的修补程序。 (2认同)

jpm*_*c26 5

一般来说,您可能已经datetime或可能datetime.date导入到某个模块中。模拟该方法的更有效方法是在导入它的模块上修补它。例子:

一个.py

from datetime import date

def my_method():
    return date.today()
Run Code Online (Sandbox Code Playgroud)

然后对于您的测试,模拟对象本身将作为参数传递给测试方法。您将使用您想要的结果值设置模拟,然后调用您的测试方法。然后你会断言你的方法做了你想要的。

>>> 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
Run Code Online (Sandbox Code Playgroud)

一句警告。毫无疑问,嘲笑是过分的。当您这样做时,它会使您的测试变得更长、更难理解且无法维护。在模拟像 一样简单的方法之前datetime.date.today,先问问自己是否真的需要模拟它。如果您的测试简短且切题并且在不模拟函数的情况下运行良好,则您可能只是查看正在测试的代码的内部细节,而不是需要模拟的对象。


frx*_*x08 5

对我来说,最简单的方法是:

from unittest import patch, Mock

def test():
    datetime_mock = Mock(wraps=datetime)
    datetime_mock.now = Mock(return_value=datetime(1999, 1, 1)
    patch('target_module.datetime', new=datetime_mock).start()
Run Code Online (Sandbox Code Playgroud)

注意此解决方案:从所有的功能datetime moduletarget_module停止工作。

  • 同意@frx08 with() 会减轻痛苦。尽管在该块内所有例如日期,timedelta 将停止工作。如果我们现在需要模拟但日期数学仍在继续怎么办?抱歉,我们必须让 .now() 只模拟而不是整个 datetime 模块。 (2认同)