断言在 Python 单元测试中调用了一个方法

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

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

有没有一种简单的方法可以断言在测试的第二行中调用了某个特定的方法(在我的例子中是 aw.Clear()) ?有没有这样的东西:

#pseudocode:
assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw))
207908 次浏览

您可以模拟出 aw.Clear,可以手动模拟,也可以使用像 Pymox这样的测试框架。手动操作,你可以使用这样的东西:

class MyTest(TestCase):
def testClear():
old_clear = aw.Clear
clear_calls = 0
aw.Clear = lambda: clear_calls += 1
aps.Request('nv2', aw)
assert clear_calls == 1
aw.Clear = old_clear

使用 pymox,你可以这样做:

class MyTest(mox.MoxTestBase):
def testClear():
aw = self.m.CreateMock(aps.Request)
aw.Clear()
self.mox.ReplayAll()
aps.Request('nv2', aw)

是的,我可以给你大纲,但我的 Python 有点生疏,我太忙了,没有时间详细解释。

基本上,您需要在调用原始方法的方法中放入一个代理,例如:

 class fred(object):
def blog(self):
print "We Blog"




class methCallLogger(object):
def __init__(self, meth):
self.meth = meth


def __call__(self, code=None):
self.meth()
# would also log the fact that it invoked the method


#example
f = fred()
f.blog = methCallLogger(f.blog)

这个关于可呼叫的 StackOverflow 回答可以帮助你理解以上内容。

更多细节:

虽然答案被接受了,但由于与格伦进行了有趣的讨论,而且有几分钟的空闲时间,我想详细说明我的答案:

# helper class defined elsewhere
class methCallLogger(object):
def __init__(self, meth):
self.meth = meth
self.was_called = False


def __call__(self, code=None):
self.meth()
self.was_called = True


#example
class fred(object):
def blog(self):
print "We Blog"


f = fred()
g = fred()
f.blog = methCallLogger(f.blog)
g.blog = methCallLogger(g.blog)
f.blog()
assert(f.blog.was_called)
assert(not g.blog.was_called)

我不知道任何内置的东西,它实现起来很简单:

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,这几乎总是正确的。

我使用 嘲笑(现在是 py3.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_onceassert_called_wiht) ,您的测试仍然可能运行,因为 Mock 会认为这是一个模拟函数,并愉快地继续下去,除非您使用 autospec=true。更多信息请阅读 断言 _ called _ once: 威胁或威胁

是的,如果你使用 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