关于单元测试:Python:如何模拟datetime.utcnow()?

Python: How do I mock datetime.utcnow()?

我有以下内容:

1
2
3
4
5
from datetime import datetime

def get_report_month_key():
    month_for_report = datetime.utcnow()
    return month_for_report.strftime("%Y%m")

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

尝试读这个,但我无法让它在utcnow()上为我工作


在您的测试文件中:

1
2
3
4
5
6
7
8
9
10
11
12
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)


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

mymodule.py

1
2
3
4
5
from datetime import datetime

def after_y2k():
    y2k = datetime(2000, 1, 1)
    return y2k < datetime.utcnow()

test_mymodule.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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())


修补内置Python模块时的效果也很复杂(与datetime一样,请参阅https://solidgeargroup.com/mocking-the-time或https://nedbatchelder.com/blog/ 201209 / mocking_datetimetoday.html或https://gist.github.com/rbarrois/5430921)将函数包装在自定义函数中,然后可以轻松修补。

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

1
2
3
4
5
import datetime


def get_utc_now():
    return datetime.datetime.utcnow()

然后,修补这个就像这样简单

1
2
3
4
5
6
7
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

使用patch装饰器而不是上下文管理器可以类似地工作。


您可以尝试使用freezetime模块。

1
2
3
4
5
6
7
8
9
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')

如果您的代码在另一个文件中,则需要修改导入发生的位置(让我们调用您的文件file1.py):

1
2
3
4
5
6
7
from file1 import get_report_month_key
import mock

@mock.patch("get_report_month_key.datetime.utcnow")
def test_get_report_month_key(mock_utcnow):
    mock_utcnow.return_value ="your value"
    assert get_report_month_key() =="your expected value"

当然,我会用unittest框架包装它。