Python:我如何模拟datetime.utcnow()?

Ste*_*ong 12 python unit-testing mocking

我有以下内容:

from datetime import datetime

def get_report_month_key():
    month_for_report = datetime.utcnow()
    return month_for_report.strftime("%Y%m") 
Run Code Online (Sandbox Code Playgroud)

如何模拟datetime.utcnow()以便我可以在此函数上编写单元测试?

试着读这一个,但我无法得到它的工作对我来说在utcnow()

小智 25

在您的测试文件中:

from yourfile import get_report_month_key
import mock
import unittest
from datetime import datetime

class TestCase(unittest.TestCase):

    @mock.patch('yourfile.datetime')
    def test_dt(self, mock_dt):
        mock_dt.utcnow = mock.Mock(return_value=datetime(1901, 12, 21))
        r = get_report_month_key()
        self.assertEqual('190112', r)
Run Code Online (Sandbox Code Playgroud)

  • 只要您不使用“datetime”中的其他内容,这就会起作用;否则请参阅/sf/answers/3584918991/(我认为这应该是公认的答案)。(当然,更好的方法是不直接使用“datetime.utcnow()”,而是将其包装在注入的服务中。作为折衷方案,/sf/answers/3205960551/ 中描述的方法可以是用过的。) (2认同)

Dir*_*irk 11

当修补内置 Python 模块变得很复杂时,什么也有效(就像使用datetime,参见例如https://solidgeargroup.com/mocking-the-timehttps://nedbatchelder.com/blog/201209/mocking_datetimetoday .htmlhttps://gist.github.com/rbarrois/5430921)正在将函数包装在自定义函数中,然后可以轻松修补。

因此,datetime.datetime.utcnow()您可以使用类似的函数,而不是调用

import datetime


def get_utc_now():
    return datetime.datetime.utcnow()
Run Code Online (Sandbox Code Playgroud)

然后,修补这个很简单

import datetime

# use whatever datetime you need here    
fixed_now = datetime.datetime(2017, 8, 21, 13, 42, 20)
with patch('your_module_name.get_utc_now', return_value=fixed_now):
    # call the code using get_utc_now() here
    pass
Run Code Online (Sandbox Code Playgroud)

使用patch装饰器而不是上下文管理器的工作方式类似。

  • 非常便利。但是请注意[日期时间文档](https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow)中的警告,因此最好使用`return datetime.datetime.now(datetime. timezone.utc)` *因为许多 `datetime` 方法将简单的 `datetime` 对象视为本地时间,所以最好使用感知日期时间来表示 UTC 时间。因此,创建表示 UTC 当前时间的对象的推荐方法是调用“datetime.now(timezone.utc)”。* (2认同)

Rya*_*ier 8

如果未datetime在要测试的模块中创建任何实例,则dasjotre接受的答案有效。如果尝试创建一个datetime对象,它将创建一个Mock对象,而不是在标准datetime对象上使用预期方法的对象。这是因为它用模拟代替了整个类的定义。代替执行此操作,您可以使用类似的方法通过将其datetime用作基础来创建模拟定义。

mymodule.py

from datetime import datetime

def after_y2k():
    y2k = datetime(2000, 1, 1)
    return y2k < datetime.utcnow()
Run Code Online (Sandbox Code Playgroud)

test_mymodule.py

import unittest
import datetime
from mock import patch, Mock
import mymodule
from mymodule import after_y2k


class ModuleTests(unittest.TestCase):
    @patch.object(mymodule, 'datetime', Mock(wraps=datetime.datetime))
    def test_after_y2k_passes(self):
        # Mock the return and run your test (Note you are doing it on your module)
        mymodule.datetime.utcnow.return_value = datetime.datetime(2002, 01, 01)
        self.assertEqual(True, after_y2k())

        mymodule.datetime.utcnow.return_value = datetime.datetime(1999, 01, 01)
        self.assertEqual(False, after_y2k())

    @patch('mymodule.datetime')
    def test_after_y2k_fails(self, mock_dt):
        # Run your tests
        mock_dt.utcnow = Mock(return_value=datetime.datetime(2002, 01, 01))
        self.assertEqual(True, after_y2k())

        # FAILS!!! because the object returned by utcnow is a MagicMock w/o 
        # datetime methods like "__lt__"
        mock_dt.utcnow = Mock(return_value=datetime.datetime(1999, 01, 01))
        self.assertEqual(False, after_y2k())
Run Code Online (Sandbox Code Playgroud)


小智 5

您可以尝试使用 freezetime 模块。

from yourfile import get_report_month_key
from freezegun import freeze_time
import unittest

class TestCase(unittest.TestCase):

    @freeze_time('2017-05-01')
    def get_report_month_key_test():
       get_report_month_key().should.equal('201705')
Run Code Online (Sandbox Code Playgroud)