32

好的,我知道手册
中提到了这一点,并且可能与和/或有关,​​但是一个简单直接的示例将极大地帮助我。 side_effectreturn_value

我有:

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()

现在,我想对这个UUT类进行单元测试,并模拟ClassToPatch. 知道UUT该类将恰好实例化两个ClassToPatch对象,我希望 Mock 框架为每个实例化返回一个新的 Mock 对象,这样我以后就可以分别断言对每个对象的调用。

如何@patch在测试用例中使用装饰器来实现这一点?即,如何修复以下代码示例?

class TestCase1(unittest.TestCase):

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

2 回答 2

37

这是一个快速的'n'dirty示例来帮助您:

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()

我已经添加test_property到 UUT 以便单元测试做一些有用的事情。现在,没有 mocktest_property应该是一个包含两个ClassToPatch实例 id 的元组。但是对于模拟,它应该是元组:("funky", "monkey")

我使用了side_effect模拟对象的属性,以便在初始化程序ClassToPatch中的每次调用时返回一个不同的实例。UUT

希望这可以帮助。

编辑:哦,顺便说一句,当我运行单元测试时,我得到:

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

OK
于 2012-05-25T09:25:52.760 回答
2

这是另一个更通用的版本,可以处理创建的任意数量的实例:

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)

如果您需要对每个实例进行更彻底的验证,您可以访问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())
于 2019-11-24T15:45:39.107 回答