15

我正在测试一个从另一个非常复杂的类继承的类,它具有数据库连接方法和一堆依赖项。我想模拟它的基类,这样我就可以很好地使用子类中定义的方法,但是在我从模拟类继承的那一刻,对象本身变成了一个模拟并丢失了它的所有方法。

我怎样才能模拟一个超类?

这种情况或多或少可以概括为:

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

这是一个简化的案例。实际发生的是RealClassextends AnotherClass,但我设法拦截AnotherClass并用模拟替换它。

4

2 回答 2

19

这是我长期以来一直在努力解决的问题,但我想我终于找到了解决方案。

正如您已经注意到的那样,如果您尝试用 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

因此,例如,您可能有一些这样的代码(抱歉,这有点做作,只是假设BaseClass并且SecondClass正在做不平凡的工作并包含许多方法,甚至根本不必由您定义):

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

因此,基类中存在的方法仍然可以作为可以与之交互的模拟,但您的类本身不会成为模拟。

而且我一直小心翼翼地确保这在 python2 和 python3 中都有效。

于 2015-02-23T02:30:40.750 回答
5

这应该适合你。

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
于 2013-11-06T16:47:29.260 回答