使用Python单元测试库(unittest,mock),如何断言是否在类a的方法中调用了类B的方法?
假设以下设置:使用Python单元测试库(unittest,mock),如何断言是否在类a的方法中调用了类B的方法?,python,python-3.x,unit-testing,mocking,Python,Python 3.x,Unit Testing,Mocking,假设以下设置: class A: def __init__(self, nodes): self.nodes=nodes def update(self, bool_a=True): if bool_a: for n in self.nodes: if hasattr(self.nodes[n], 'update'): self.nodes[n].up
class A:
def __init__(self, nodes):
self.nodes=nodes
def update(self, bool_a=True):
if bool_a:
for n in self.nodes:
if hasattr(self.nodes[n], 'update'):
self.nodes[n].update()
class B:
def __init__(self, int_attr=5):
self.int_attr=int_attr
def update(self):
self.int_attr = 0
让我们假设类A中的节点列表实际上是类B的实例列表
如何为类a的更新方法编写单元测试,以检查是否调用了类a的self.nodes中包含的每个类B节点的更新方法
在更一般的设置中,让我们假设有多个类实现更新方法,并且可以是类a的self.nodes中的节点。如何检查self.nodes成员的所有更新方法都已调用
我尝试了以下方法,但没有成功:
mock_obj = MagicMock()
@patch('module.A.update', return_value=mock_obj)
def test_update(self, mock_obj):
nodes = {}
nodes['first'] = B(int_attr=1)
nodes['second'] = B(int_attr=2)
test_A = module.A(nodes=nodes)
test_A.update(bool_A=True)
self.assertTrue(mock_obj.called)
如中所述
编辑:如果我们假设这种特殊情况:
import unittest
import mock
from unittest import TestCase
class A:
def __init__(self, nodes):
self.nodes=nodes
def update(self, bool_a=True):
if bool_a:
to_update = [n for n in self.nodes]
while len(to_update) > 0:
if hasattr(self.nodes[to_update[-1]], 'update'):
self.nodes[to_update[-1]].update()
print('Update called.')
if self.nodes[to_update[-1]].is_updated:
to_update.pop()
class B:
def __init__(self, int_attr=5):
self.int_attr=int_attr
self.is_updated = False
def update(self):
self.int_attr = 0
self.is_updated = True
class TestEnsemble(TestCase):
def setUp(self):
self.b1 = B(1)
self.b2 = B(2)
self.b3 = B(3)
self.nodes = {}
self.nodes['1'] = self.b1
self.nodes['2'] = self.b2
self.nodes['3'] = self.b3
self.a = A(self.nodes)
@mock.patch('module.B.update')
def test_update(self, mock_update):
mock_update.return_value = None
self.a.update()
with self.subTest():
self.assertEqual(mock_update.call_count, 3)
在这种情况下运行unittest会导致无休止的循环,因为is_updated属性永远不会设置为True,因为类B的更新方法是模拟的。在这种情况下,如何度量在A.update中调用B.update的时间量
更新:
我试过这个:
@mock.patch('dummy_script.B')
def test_update(self, mock_B):
self.a.update()
with self.subTest():
self.assertEqual(mock_B.update.call_count, 3)
update函数确实运行了3次,现在我在控制台输出中看到它,正如update调用的那样。打印三次,但update方法的调用计数保持为零。我是否检查了错误的属性/对象
如何为TestA.test\u update编写单元测试,以查看是否调用了B.update
这只是给一些想法
import mock
import unittest
import A
import B
class TestB(unittest.TestCase):
# only mock away update method of class B, this is python2 syntax
@mock.patch.object(B, 'update')
def test_update(self, mockb_update):
# B.update() does not return anything
mockb_update.return_value = None
nodes = {}
nodes['first'] = B(int_attr=1)
nodes['second'] = B(int_attr=2)
test_A = A(nodes)
test_A.update(bool_A=True)
self.assertTrue(mockb_update.called)
如何检查为所有A节点调用的所有B.update
在OP更新代码后,当B.is________________________________
mock a B.是在uu init uu或mock class内更新的uu init uu是一个比原始帖子更复杂的主题
这里有一些想法,B.is_更新不能只是mock.patch,它只有在类B启动后才可用。因此,我们的选择是
模拟B.。uuu初始化,或类构造函数
模拟整个b类,在你的例子中更容易,集合更新为真,将结束无休止的循环。结合@Gang和@jornsharpe的答案,以下代码片段解决了我上面提出的问题: 如何为TestA.test\u update编写单元测试,以查看是否调用了B.update? 见“帮派”的答案 如何检查为所有A节点调用的所有B.update? 见“帮派”的答案 在OP更新代码后,当B.is________________________________ 正如@jornsharpe所建议的,这里的解决方案是在节点中为每个B实例创建一个模拟对象,并分别检查函数调用:
class TestA(TestCase):
@mock.patch('module.B')
@mock.patch('module.B')
@mock.patch('module.B')
def test_update(self, mock_B1, mock_B2, mock_B3):
nodes = {}
nodes['1'] = mock_B1
nodes['2'] = mock_B2
nodes['3'] = mock_B3
a = A(nodes)
a.update()
with self.subTest():
self.assertEqual(mock_B1.update.call_count, 1)
with self.subTest():
self.assertEqual(mock_B2.update.call_count, 1)
with self.subTest():
self.assertEqual(mock_B3.update.call_count, 1)
此外,如果您出于某种原因想要执行模拟函数,以防它们设置了一些影响运行时的标志或变量,可以编写如下测试:
def test_fit_skip_ancestors_all(self):
nodes = {}
nodes['1'] = mock_B1
nodes['2'] = mock_B2
nodes['3'] = mock_B3
a = A(nodes)
with mock.patch.object(A.nodes['1'],'update',wraps=A.nodes['1'].update) as mock_B1, \
mock.patch.object(A.nodes['2'], 'update', wraps=A.nodes['2'].update) as mock_B2, \
mock.patch.object(A.nodes['3'], 'update', wraps=A.nodes['3'].update) as mock_B3:
a.update()
with self.subTest():
self.assertEqual(mock_B1.call_count, 1)
with self.subTest():
self.assertEqual(mock_B2.call_count, 1)
with self.subTest():
self.assertEqual(mock_B3.call_count, 1)
你永远不应该嘲笑你实际上要测试的东西,那是完全倒退的。你为什么不建立一个模拟B的列表并把它传递给别人呢?另外,请注意,您的测试和生产代码中的可变对象存在重大问题。好吧,我这里只使用了一个列表作为示例,我同意这并不理想。实际上,它是一本字典,无论如何也是可变的。我更新了问题。然后制作一本模拟词典,并把它们传下去;想法是一样的,完全没有必要在这里修补模块。你介意帮我写一个简短的代码片段吗?正如您所看到的,我对单元测试非常陌生…:/抱歉打扰了:这似乎有效!如果B.update返回一个非平凡的值,有什么区别吗?如果节点包含多个不同类型的类,比如B、C和D,它将如何工作?谢谢@ElBrutale,mockb_update.return_值在这里没有任何区别,默认值是一个Mock对象。如果节点是不同类型的类,您可以尝试多个修补程序,或者修补.update以获得最小覆盖率,例如方法exists、call_args、call_with等。非常感谢,这非常有用。如果你觉得很大方,我编辑了一个问题案例,其中一个属性在mocked方法中更新,我希望得到一些建议:我编辑了这个问题,不能在评论中发布代码。。。再次感谢你的帮助!
def test_fit_skip_ancestors_all(self):
nodes = {}
nodes['1'] = mock_B1
nodes['2'] = mock_B2
nodes['3'] = mock_B3
a = A(nodes)
with mock.patch.object(A.nodes['1'],'update',wraps=A.nodes['1'].update) as mock_B1, \
mock.patch.object(A.nodes['2'], 'update', wraps=A.nodes['2'].update) as mock_B2, \
mock.patch.object(A.nodes['3'], 'update', wraps=A.nodes['3'].update) as mock_B3:
a.update()
with self.subTest():
self.assertEqual(mock_B1.call_count, 1)
with self.subTest():
self.assertEqual(mock_B2.call_count, 1)
with self.subTest():
self.assertEqual(mock_B3.call_count, 1)