简体   繁体   English

Python 模拟:mocking 基础 class 为 inheritance

[英]Python mock: mocking base class for inheritance

I am testing a class that inherits from another one very complex, with DB connection methods and a mess of dependences.我正在测试一个 class,它从另一个非常复杂的继承而来,具有 DB 连接方法和一堆依赖项。 I would like to mock its base class so that I can nicely play with the method defined in the subclass, but in the moment I inherit from a mocked class, the object itself turns a mock and loses all its methods.我想模拟它的基数 class 以便我可以很好地使用子类中定义的方法,但是在我从模拟的 class 继承的那一刻,object 本身变成了一个模拟并丢失了它的所有方法。

How can I mock a superclass?我如何模拟超类?

More or less the situation can be summed up in this:大致情况可以概括为:

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='...'>

This is a simplified case.这是一个简化的案例。 What is actually happening is that RealClass extends AnotherClass , but I managed to intercept the AnotherClass and replace it with a mock.实际发生的是RealClass扩展AnotherClass ,但我设法拦截了AnotherClass并将其替换为模拟。

This is something I've been struggling with for a long time, but I think I've finally found a solution.这是我一直在努力解决的问题,但我想我终于找到了解决方案。

As you already noticed, if you try to replace the base class with a Mock, the class you're attempting to test simply becomes the mock, which defeats your ability to test it.正如您已经注意到的,如果您尝试用 Mock 替换基类,则您尝试测试的类只会变成模拟,这会破坏您测试它的能力。 The solution is to mock only the base class's methods rather than the entire base class itself, but that's easier said than done: it can be quite error prone to mock every single method one by one on a test by test basis.解决方案是只模拟基类的方法而不是整个基类本身,但这说起来容易做起来难:在逐个测试的基础上一个一个地模拟每个方法可能很容易出错。

What I've done instead is created a class that scans another class, and assigns to itself Mock() s that match the methods on the other class.我所做的是创建一个类来扫描另一个类,并将与另一个类上的方法匹配的Mock()分配给自己。 You can then use this class in place of the real base class in your testing.然后,您可以在测试中使用此类代替真正的基类。

Here is the fake class:这是假的类:

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

So for example you might have some code like this (apologies this is a little bit contrived, just assume that BaseClass and SecondClass are doing non-trivial work and contain many methods and aren't even necessarily defined by you at all):因此,例如您可能有一些这样的代码(抱歉,这有点做作,假设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()

You would then be able to write some tests like this:然后你就可以写一些这样的测试:

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

So the methods that exist on the base classes remain available as mocks you can interact with, but your class does not itself become a mock.因此,基类中存在的方法仍可用作您可以与之交互的模拟,但您的类本身不会成为模拟。

And I've been careful to ensure that this works in both python2 and python3.我一直很小心地确保这在 python2 和 python3 中都有效。

This should work for you.这应该对你有用。

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='...'>

You should'nt pass an instance of the class as you did.你不应该像你那样传递类的实例。 mock.MagicMock is a class, so you pass it directly. mock.MagicMock是一个类,所以你直接传递它。

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

I was facing a similar problem and was able to do this via @patch.object .我遇到了类似的问题,并且能够通过@patch.object做到这一点。 See examples for patch decorators in the official python doc.请参阅官方 python 文档中补丁装饰器的示例。

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

Just exemplifying @ Akash 's answer, which was the one that in fact solved my inheritance mock challenge:只是举例说明@Akash的回答,这实际上解决了我的 inheritance 模拟挑战:

 @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 

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

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