使用模拟对象修补超类方法

5

这里有许多关于如何在Python中对您的类的超类进行测试修补的类似问题。我从这些问题中获得了一些想法,但仍未达到我所需要的。

假设我有两个基类:

class Foo(object):
    def something(self, a):
        return a + 1

class Bar(object):
    def mixin(self):
        print("Hello!")

现在我定义要测试的类如下所示:
class Quux(Foo, Bar):
    def something(self, a):
        self.mixin()
        return super().something(a) + 2

假设我想测试mixin是否被调用,并且我想要替换模拟的Foo.something的返回值,但重要的是(也是必须的)我不想改变Quux.something中的任何控制流或逻辑。 假设修补超类“只是起作用了”,我尝试使用unittest.mock.patch

with patch("__main__.Foo", spec=True) as mock_foo:
    with patch("__main__.Bar", spec=True) as mock_bar:
        mock_foo.something.return_value = 123
        q = Quux()
        assert q.something(0) == 125
        mock_bar.mixin.assert_called_once()

这不起作用:当实例化Quux时,超类对somethingmixin的定义没有被模拟,这并不令人意外,因为类的继承是在补丁之前定义的。
至少我可以通过显式设置mixin来解决mixin问题。
# This works to mock the mixin method
q = Quux()
setattr(q, "mixin", mock_bar.mixin)

然而,类似的方法并不能用于被覆盖的方法something

就像我之前提到的,其他回答这个问题的人建议使用模拟对象来覆盖Quux__bases__值。然而,这根本行不通,因为__bases__必须是一组类的元组,而模拟对象的类似乎只是原始类的副本:

# This doesn't do what I want
Quux.__bases__ = (mock_foo.__class__, mock_bar.__class__)
q = Quux()

其他回答建议覆盖super。这样做确实有效,但我觉得有点危险,因为你不想打补丁的任何对super的调用可能会破坏程序的正常运行。

那么除了这种方法,有没有更好的办法来实现我的需求呢?

with patch("builtins.super") as mock_super:
    mock_foo = MagicMock(spec=Foo)
    mock_foo.something.return_value = 123
    mock_super.return_value = mock_foo

    mock_bar = MagicMock(spec=Bar)

    q = Quux()
    setattr(q, "mixin", mock_bar.mixin)

    assert q.something(0) == 125
    mock_bar.mixin.assert_called_once()
1个回答

1
事实上很简单 - 子类将在其自身结构中包含对原始类的引用(公共可见属性__bases____mro__)。当您模拟这些基类时,该引用不会更改 - 模拟只会影响显式使用这些对象的人,而补丁则会“打开”。换句话说,它们只有在您的Quux类本身在with块内定义时才会被使用。但这也行不通,因为替换类的“模拟”对象不能是一个适当的超类。
然而,解决方法和正确的方法非常简单 - 您只需要模拟要替换的方法,而不是类。
问题现在有点老了,我希望您已经解决了,但是正确的做法是:
with patch("__main__.Foo.something", spec=True) as mock_foo:
    with patch("__main__.Bar.mixin", spec=True) as mock_bar:
        mock_foo.return_value = 123
        q = Quux()
        assert q.something(0) == 125
        mock_bar.assert_called_once()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接