简体   繁体   English

你如何模拟 python 类的补丁并为每个实例获取一个新的 Mock 对象?

[英]How do you mock patch a python class and get a new Mock object for each instantiation?

OK,行,
I know this is mentioned in the manual , and probably has to do with side_effect and/or return_value , but a simple, direct example will help me immensely.我知道手册中提到了这一点,可能与side_effect和/或return_value ,但是一个简单、直接的示例将极大地帮助我。

I have:我有:

class ClassToPatch():
   def __init__(self, *args):
       _do_some_init_stuff()

   def some_func():
       _do_stuff()


class UUT():
    def __init__(self, *args)
       resource_1 = ClassToPatch()
       resource_2 = ClassToPatch()

Now, I want to unit test the UUT class, and mock the ClassToPatch .现在,我想对UUT类进行单元测试,并模拟ClassToPatch Knowing the UUT class will instantiate exactly two ClassToPatch objects, I want the Mock framework to return a new Mock object for each instantiation, so I can later assert calls on each separately.知道UUT类将实例化两个ClassToPatch对象,我希望 Mock 框架为每个实例化返回一个新的 Mock 对象,以便我稍后可以分别断言对每个对象的调用。

How do I achieve this using the @patch decorator in a test case?如何在测试用例中使用@patch装饰器来实现这一点? Namely, how to fix the following code sample?即,如何修复以下代码示例?

class TestCase1(unittest.TestCase):

    @patch('classToPatch.ClassToPatch',autospec=True)
    def test_1(self,mock1,mock2):
        _assert_stuff()

Here's a quick'n'dirty example to get you going:这是一个让您开始的快速示例:

import mock
import unittest

class ClassToPatch():
   def __init__(self, *args):
       pass

   def some_func(self):
       return id(self)

class UUT():
    def __init__(self, *args):
        resource_1 = ClassToPatch()
        resource_2 = ClassToPatch()
        self.test_property = (resource_1.some_func(), resource_2.some_func())

class TestCase1(unittest.TestCase):
    @mock.patch('__main__.ClassToPatch', autospec = True)
    def test_1(self, mock1):
        ctpMocks = [mock.Mock(), mock.Mock()]
        ctpMocks[0].some_func.return_value = "funky"
        ctpMocks[1].some_func.return_value = "monkey"
        mock1.side_effect = ctpMocks

        u = UUT()
        self.assertEqual(u.test_property, ("funky", "monkey"))

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

I've added test_property to UUT so that the unit test does something useful.我已经将test_property添加到 UUT 以便单元测试做一些有用的事情。 Now, without the mock test_property should be a tuple containing the ids of the two ClassToPatch instances.现在,没有模拟test_property应该是一个包含两个ClassToPatch实例的 id 的元组。 But with the mock it should be the tuple: ("funky", "monkey") .但是对于模拟,它应该是元组: ("funky", "monkey")

I've used the side_effect property of the mock object so that a different instance of ClassToPatch is returned on each call in the UUT initialiser.我使用了模拟对象的side_effect属性,以便在UUT初始化程序中的每次调用时返回ClassToPatch的不同实例。

Hope this helps.希望这可以帮助。

Edit: Oh, by the way, when I run the unit test I get:编辑:哦,顺便说一句,当我运行单元测试时,我得到:

.
----------------------------------------------------------------------
Ran 1 test in 0.004s

OK

Here is another version which is more generic to handle any number of instances created:这是另一个更通用的版本,可以处理创建的任意数量的实例:

class TestUUT:
    def test_init(self, mocker):
        class MockedClassToPatchMeta(type):
            static_instance = mocker.MagicMock(spec=ClassToPatch)

            def __getattr__(cls, key):
                return MockedClassToPatchMeta.static_instance.__getattr__(key)

        class MockedClassToPatch(metaclass=MockedClassToPatchMeta):
            original_cls = ClassToPatch
            instances = []

            def __new__(cls, *args, **kwargs):
                MockedClassToPatch.instances.append(
                    mocker.MagicMock(spec=MockedClassToPatch.original_cls))
                MockedClassToPatch.instances[-1].__class__ = MockedClassToPatch
                return MockedClassToPatch.instances[-1]

        mocker.patch(__name__ + '.ClassToPatch', new=MockedClassToPatch)

        UUT()

        # since your original code created two instances
        assert 2 == len(MockedClassToPatch.instances)

If you need more thorough validation for each instance you can access MockedClassToPatch.instances[0] or MockedClassToPatch.instances[1] .如果您需要对每个实例进行更彻底的验证,您可以访问MockedClassToPatch.instances[0]MockedClassToPatch.instances[1]

I've also created a helper library to generate the meta class boilerplate for me.我还创建了一个帮助程序库来为我生成元类样板。 To generate the needed code for your example I wrote:要为您的示例生成所需的代码,我写道:

print(PytestMocker(mocked=ClassToPatch, name=__name__).mock_classes().mock_classes_static().generate())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM