python unittest 的设置函数不使用在 class 上声明的模拟

setUp function for python unittest doesn't use mocks declared over the class

所以我正在编写单元测试,但在使用 setUp 函数时遇到了问题。据我所见,它应该只是在你的函数之前执行代码,因此我可以把任何重复的东西放在那里。然而,这个函数似乎并没有在整个 class 上应用我作为补丁装饰器创建的模拟。这是我希望它看起来像的一小部分:

@patch('geomet_data_registry.layer.base.get_today_and_now', new=mocked_get_date)  # noqa
@patch('geomet_data_registry.layer.base.load_plugin', new=mocked_load_plugin)
@patch('geomet_data_registry.layer.base.TILEINDEX_PROVIDER_DEF', new=mocked_tileindex)  # noqa
@patch('geomet_data_registry.layer.base.STORE_PROVIDER_DEF', new=mocked_store)
class TestInitBase(unittest.TestCase):

    def setUp(self):
        """ Code that executes before every function. """

        self.maxDiff = None
        self.today_date = \
            datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
        mocked_get_date.return_value = self.today_date
        self.base_layer = BaseLayer({'name': 'model_gem_global'})

    def test_Init(self):
        
        expected_values = {'items': [],...

        base_layer_attr = self.base_layer.__dict__

        self.assertDictEqual(expected_values, base_layer_attr, msg=None)

这里我模拟了收到的日期,这样它就不会影响我的测试,我模拟了 load_plugin,当使用 returns 某个插件的 class 实例时,我模拟 TileIndex,这是一个 ES TileIndex,我模拟商店,这是一个 redis 商店。如果我使用上面显示的代码,它不起作用。当我在 setUp 中实例化 class BaseLayer 时,使用了我的模拟的 none 并且我得到:

-  'receive_datetime': '2021-11-10T12:56:07.371067Z',
?                                              ^^^
+  'receive_datetime': '2021-11-10T12:56:07.371131Z',
?                                              ^^^
-  'store': <MagicMock name='mock()' id='140158154534472'>,
-  'tileindex': <MagicMock name='mock()' id='140158154534472'>,
+  'store': <BaseStore> Redis,
+  'tileindex': <ElasticsearchTileIndex> http://localhost:9200,

然而,在你告诉我也许我的路径对于模拟或类似的东西是错误的之前,我可以向你保证一切正常,因为如果我保持一切相同代码工作,除了我重复 class 在每个测试函数中实例化。此外,如果我保持不变,它将起作用,但是我将 setUp 命名为 mySetUp 并在函数的开头调用它。

一切都是这样工作的,我已经在完全没有使用 setUp 的情况下完成了所有测试,因为我记得自己在想“这东西坏了我不会冒险在我的测试中使用这个功能".

谢谢!

问题是 mock.patch 装饰器应用于每个测试函数,并且在 setUp 期间尚未完成修补。 要对所有测试使用相同的模拟,您必须 start/stop 在 setUp/tearDown 中模拟。这可能看起来像:

class TestInitBase(unittest.TestCase):

    def setUp(self):
        self.data_patcher = patch('geomet_data_registry.layer.base.get_today_and_now',
            new=mocked_get_date)
        self.data_patcher.start()
        self.plugin_patcher = patch('geomet_data_registry.layer.base.load_plugin',
            new=mocked_load_plugin)
        self.plugin_patcher.start()
        ...

        self.today_date = \
            datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
        mocked_get_date.return_value = self.today_date
        self.base_layer = BaseLayer({'name': 'model_gem_global'})

    def tearDown(self):
        self.data_patcher.stop()
        self.plugin_patcher.stop()
        ...

诚然,这不如使用装饰器好。您仍然可以将装饰器用于 setUp 中不必修补的函数,如果有的话。

附带说明:使用 pytest 这会不那么混乱,因为您可以在同一装置中进行设置和拆卸部分。