在Python单元测试中断言某方法被调用

145 投票
5 回答
267553 浏览
提问于 2025-04-16 04:48

假设我在一个Python单元测试中有以下代码:

aw = aps.Request("nv1")
aw2 = aps.Request("nv2", aw)

有没有简单的方法可以检查在测试的第二行中,某个特定的方法(在我的例子中是 aw.Clear())是否被调用了?比如说,有没有类似这样的东西:

#pseudocode:
assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw))

5 个回答

14

我不知道有没有现成的功能可以用。不过实现起来其实很简单:

class assertMethodIsCalled(object):
    def __init__(self, obj, method):
        self.obj = obj
        self.method = method

    def called(self, *args, **kwargs):
        self.method_called = True
        self.orig_method(*args, **kwargs)

    def __enter__(self):
        self.orig_method = getattr(self.obj, self.method)
        setattr(self.obj, self.method, self.called)
        self.method_called = False

    def __exit__(self, exc_type, exc_value, traceback):
        assert getattr(self.obj, self.method) == self.called,
            "method %s was modified during assertMethodIsCalled" % self.method

        setattr(self.obj, self.method, self.orig_method)

        # If an exception was thrown within the block, we've already failed.
        if traceback is None:
            assert self.method_called,
                "method %s of %s was not called" % (self.method, self.obj)

class test(object):
    def a(self):
        print "test"
    def b(self):
        self.a()

obj = test()
with assertMethodIsCalled(obj, "a"):
    obj.b()

这段代码需要确保对象本身不会修改 self.b,通常来说这是几乎总是成立的。

36

如果你使用的是Python 3.3或更高版本,那就没问题。你可以用内置的unittest.mock来检查某个方法是否被调用。如果你用的是Python 2.6或更高版本,可以使用一个叫做Mock的库,它的功能和前面提到的差不多。

下面是一个简单的例子,适合你的情况:

from unittest.mock import MagicMock
aw = aps.Request("nv1")
aw.Clear = MagicMock()
aw2 = aps.Request("nv2", aw)
assert aw.Clear.called
211

我使用的是Mock(在Python 3.3及以上版本中叫做unittest.mock)来做这个:

from mock import patch
from PyQt4 import Qt


@patch.object(Qt.QMessageBox, 'aboutQt')
def testShowAboutQt(self, mock):
    self.win.actionAboutQt.trigger()
    self.assertTrue(mock.called)

对于你的情况,它可能看起来像这样:

import mock
from mock import patch


def testClearWasCalled(self):
   aw = aps.Request("nv1")
   with patch.object(aw, 'Clear') as mock:
       aw2 = aps.Request("nv2", aw)
          
   mock.assert_called_with(42) # or mock.assert_called_once_with(42)

Mock有很多实用的功能,包括可以修改一个对象或模块,以及检查是否调用了正确的东西等等。

买家需谨慎!

如果你把assert_called_with打错了(比如打成assert_called_once或者把两个字母调换成assert_called_wiht),你的测试可能还是会运行,因为Mock会认为这是一个被模拟的函数,并且会顺利执行,除非你使用autospec=true。想了解更多信息,可以阅读assert_called_once: 威胁还是麻烦

撰写回答