python 巨蟒模仿:模仿基类以进行继承

u4dcyp6a  于 2023-01-24  发布在  Python
关注(0)|答案(4)|浏览(180)

我正在测试一个继承自另一个非常复杂的类的类,它有DB连接方法和一堆依赖关系,我想模拟它的基类,这样我就可以很好地使用子类中定义的方法,但是当我从一个被模拟的类继承时,对象本身变成了一个模拟对象,失去了它所有的方法。

我怎么能嘲笑一个超类

情况大致可以归纳如下:

import mock

ClassMock = mock.MagicMock()

class RealClass(ClassMock):

    def lol(self):
        print 'lol'

real = RealClass()
real.lol()  # Does not print lol, but returns another mock

print real # prints <MagicMock id='...'>

这是一个简化的例子,实际上是RealClass扩展了AnotherClass,但是我设法截取了AnotherClass并用mock替换了它。

3yhwsihp

3yhwsihp1#

这个问题我已经纠结了很长时间,但我想我终于找到了解决办法。
正如你已经注意到的,如果你试图用一个Mock来替换基类,你试图测试的类只是变成了一个mock,这就破坏了你测试它的能力。解决方案是只模拟基类的方法,而不是整个基类本身,但是说起来容易做起来难:在逐个测试基础上逐个模拟每一个方法是非常容易出错的。
我所做的是创建一个类来扫描另一个类,并将与另一个类上的方法相匹配的Mock()赋值给自己,然后在测试中使用这个类来代替真实的的基类。
下面是一个伪类:

class Fake(object):
    """Create Mock()ed methods that match another class's methods."""

    @classmethod
    def imitate(cls, *others):
        for other in others:
            for name in other.__dict__:
                try:
                    setattr(cls, name, Mock())
                except (TypeError, AttributeError):
                    pass
        return cls

例如,您可能有一些代码,如下所示(抱歉,这有点做作,只是假设BaseClassSecondClass正在执行重要的工作,包含许多方法,甚至根本不需要您定义):

class BaseClass:
    def do_expensive_calculation(self):
        return 5 + 5

class SecondClass:
    def do_second_calculation(self):
        return 2 * 2

class MyClass(BaseClass, SecondClass):
    def my_calculation(self):
        return self.do_expensive_calculation(), self.do_second_calculation()

然后,您就可以编写一些如下所示的测试:

class MyTestCase(unittest.TestCase):
    def setUp(self):
        MyClass.__bases__ = (Fake.imitate(BaseClass, SecondBase),)

    def test_my_methods_only(self):
        myclass = MyClass()
        self.assertEqual(myclass.my_calculation(), (
            myclass.do_expensive_calculation.return_value, 
            myclass.do_second_calculation.return_value,
        ))
        myclass.do_expensive_calculation.assert_called_once_with()
        myclass.do_second_calculation.assert_called_once_with()

因此,存在于基类中的方法仍然可以作为您可以与之交互的mock来使用,但是您的类本身不会成为mock。
我已经很小心地确保这在python2和python3中都有效。

6mzjoqzu

6mzjoqzu2#

这应该对你有用。

import mock

ClassMock = mock.MagicMock # <-- Note the removed brackets '()'

class RealClass(ClassMock):

    def lol(self):
        print 'lol'

real = RealClass()
real.lol()  # Does not print lol, but returns another mock

print real # prints <MagicMock id='...'>

您不应该像以前那样传递类的示例。mock.MagicMock是一个类,所以您直接传递它。

In [2]: inspect.isclass(mock.MagicMock)
Out[2]: True
0x6upsns

0x6upsns3#

我也遇到过类似的问题,可以通过@patch.object来实现,参见python官方文档中的补丁装饰器示例。

class MyTest(unittest.TestCase):
    @patch.object(SomeClass, 'inherited_method')
    def test_something(self, mock_method):
        SomeClass.static_method()
        mock_method.assert_called_with()
von4xj4u

von4xj4u4#

下面举例说明@Akash的答案,它实际上解决了我的继承模拟挑战:

@patch.object(SomeClassInheritingAnother, "inherited_method")
    def test_should_test_something(self, mocked_inherited_method, mocker, caplog):
       #Mocking an HTTP result status code
       type(mocked_inherited_method.return_value).status_code = mocker.PropertyMock(return_value=200)

       #Calling the inherited method, that should end up using the mocked method
       SomeClassInheritingAnother.inherited_method()

       #Considering that the request result is being logged as 'Request result: {response.status_code}'
       assert "Request result: 200" in caplog.text

相关问题