【问题标题】:Python mock: mocking base class for inheritancePython mock:模拟继承的基类
【发布时间】:2013-11-06 16:33:33
【问题描述】:

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

如何模拟超类?

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

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 对象作为基类吗?一个简单的object 会起作用吗?
  • 什么是 MagicMock?类还是类生成器?通常你只会class RealClass(mock.MagicMock):

标签: python unit-testing inheritance mocking


【解决方案1】:

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

正如您已经注意到的,如果您尝试用 Mock 替换基类,那么您尝试测试的类就会变成 mock,这会破坏您对其进行测试的能力。解决方案是仅模拟基类的方法而不是整个基类本身,但这说起来容易做起来难:在逐个测试的基础上逐个模拟每个方法很容易出错。

我所做的是创建一个扫描另一个类的类,并将匹配另一个类的方法的Mock()s 分配给它自己。然后,您可以在测试中使用该类代替真正的基类。

这是假类:

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

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

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

【讨论】:

  • 很好的答案。我改进了你的类 Fake,使用 setattr(cls, name, mock.create_autospec(other.__dict__[name])) 而不是 setattr(cls, name, Mock()),以提高安全性并增加一些测试能力。
【解决方案2】:

这应该适合你。

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

【讨论】:

  • 这并不完全是我想要做的,但也许这是不可能的:我期待有一个基类的模拟,然后能够知道对模拟做了什么调用对象的一部分。这种方法的“缺点”是如果子类覆盖了 MagicMock 方法。无论如何,这就像一个魅力。谢谢
  • 我遇到了这种方法的新问题。如果我想调用应该在基类中定义的类方法RealClass.do_something 怎么办?因为 MagicMock 本身不是一个模拟,而是一个类,所以你不能给它添加神奇的新方法:S
  • 花了我一点时间来弄清楚这里有什么区别..为了帮助其他人,区别在于 ClassMock = mock.MagicMock 与 ClassMock = mock.MagicMock()
【解决方案3】:

我遇到了类似的问题,我可以通过@patch.object 做到这一点。请参阅官方 python 文档中patch decorators 的示例。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    • 2016-06-06
    相关资源
    最近更新 更多