如何模拟称为默认参数的函数

How to mock a function called as default argument

我有一个包含时间戳的 class。在 init 函数中,初始时间戳设置为提供的参数,默认值为 'time.monotonic()'。我也有和更新设置时间戳的功能,也使用 time.monotonic()

在对此 class 进行单元测试时,我想模拟 time.monotonic,以获得可预测的结果。

但是,默认参数中的调用始终是真实的 time.monotonic

ClassWithTimestamp.py:

import time

class ClassWithTimestamp:
    def __init__(self, value={}, timestamp=time.monotonic()):
        self.timestamp = timestamp

    def update(self):
        self.timestamp = time.monotonic()

ClassWithTimestampTest.py:

import unittest
from unittest import mock

import ClassWithTimestamp

class ClassWithTimestampTest(unittest.TestCase):
    def setUp(self):
        ClassWithTimestamp.time.monotonic = mock.Mock(name='now')
        ClassWithTimestamp.time.monotonic.return_value = 1000

    def tearDown(self):
        pass

    def test_init(self):
        sut = ClassWithTimestamp.ClassWithTimestamp()
        self.assertEqual(sut.timestamp, 1000)

    def test_update(self):
        sut = ClassWithTimestamp.ClassWithTimestamp()
        sut.update()
        self.assertEqual(sut.timestamp, 1000)

if __name__ == '__main__':
    unittest.main()

当运行时:

python3 ClassWithTimestampTest.py -v
test_init (__main__.ClassWithTimestampTest) ... FAIL
test_update (__main__.ClassWithTimestampTest) ... ok

======================================================================
FAIL: test_init (__main__.ClassWithTimestampTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "ClassWithTimestampTest.py", line 16, in test_init
    self.assertEqual(sut.timestamp, 1000)
AssertionError: 762811.874163785 != 1000

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

我发现了这个:

"Least Astonishment" and the Mutable Default Argument

它解释了我的问题,默认参数是在定义 init 函数时调用的,而不是执行它。

所以我的代码一开始就有错误,因为我打算将时间戳设置为构建时间。

我修改后的代码是

def __init__(self, value={}, timestamp=None):
    if timestamp:
        self.timestamp = timestamp
    else:
        self.timestamp = time.monotonic()

问题本身仍然存在,我看到的唯一方法是在构造后用模拟函数的输出覆盖 class 的成员。