我正在测试的代码是这样的:
def to_be_tested(x):
return round((x.a + x.b).c())
我想通过将Mock对象作为x传递来测试它。我试着这样做:
import unittest
import unittest.mock
class Test_X(unittest.TestCase):
def test_x(self):
m = unittest.mock.Mock()
to_be_tested(m)
# now check if the proper call has taken place on m
对x.a
的呼叫和x.b
的呼叫按预期工作。他们提供了新的模拟对象,可以询问它们是如何创建的(例如,通过q._mock_parent
和q._mock_new_name
),所以这一步很好。
但是应该发生添加只会引发错误(TypeError: unsupported operand type(s) for +: 'Mock' and 'Mock'
)。我希望这也会返回一个模拟对象,以便调用.c()
并且(再次)返回一个模拟对象。
在调用被测代码之前我也考虑了m.__add__ = lambda a, b: unittest.mock.Mock(a, b)
,但这不会有帮助,因为我的原始Mock不会被添加,而是新创建的。
我也试过(已经非常繁琐)m.a.__add__ = lambda a, b: unittest.mock.Mock(a, b)
。但是(令我惊讶的是)在调用被测代码时引发了AttributeError: Mock object has no attribute 'c'
。我不理解,因为我在那里创建的模拟应该接受我在其中调用了c()
,对吗?
有没有办法可以实现我的目标?如何创建一个能够添加到另一个Mock的模拟?
或者是否有其他标准方法对我的代码进行单元测试?
编辑:我没有兴趣提供一个专门的代码,为预期的调用准备传递的模拟。我只想通过检查传递和返回的模拟对象,在调用之后检查所有事情是否按预期发生。我认为这种方式应该是可能的,在这种情况下(以及其他类似的复杂情况)我可以使用它。
答案 0 :(得分:1)
添加对象要求这些对象至少实现__add__
,一种特殊方法,Mock称为魔术方法,请参阅文档中的Mocking Magic Methods section:
由于魔术方法与普通方法的查找方式不同,因此专门实施了此支持。这意味着只支持特定的魔术方法。支持的列表几乎包括所有这些列表。如果您有任何遗漏,请告诉我们。
访问mock
支持的魔术方法的最简单方法是,您可以创建MagicMock
class的实例,它为这些提供默认实现(每个都返回一个{{1}默认情况下实例)。
这使您可以访问MagicMock
来电:
x.a + x.b
已记录对>>> from unittest import mock
>>> m = mock.MagicMock()
>>> m.a + m.b
<MagicMock name='mock.a.__add__()' id='4500141448'>
>>> m.mock_calls
[call.a.__add__(<MagicMock name='mock.b' id='4500112160'>)]
的调用,其参数为m.a.__add__()
;这是我们现在可以在测试中断言的东西!
然后,使用相同的m.b
模拟来提供m.a.__add__()
模拟:
.c()
同样,这是我们可以断言的。请注意,如果你重复这个电话,你会发现嘲笑是单身;当访问属性或调用mock时,会创建更多相同类型的模拟并存储,稍后您可以使用这些存储的对象断言正确的对象已被分发;您可以使用Mock.return_value
attribute
>>> (m.a + m.b).c()
<MagicMock name='mock.a.__add__().c()' id='4500162544'>
现在,转到>>> m.a.__add__.return_value.c.return_value
<MagicMock name='mock.a.__add__().c()' id='4500162544'>
>>> (m.a + m.b).c() is m.a.__add__.return_value.c.return_value
True
。 round()
也称为魔术方法__round__()
method。不幸的是,这不在支持的方法列表中:
round()
这可能是一种疏忽,因为其他数字方法(例如>>> round(mock.MagicMock())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type MagicMock doesn't define __round__ method
和__trunc__
)都包含在中。我提交了a bug report to request it to be added。您可以使用以下命令手动将其添加到__ceil__
支持的方法列表中:
MagicMock
mock._magics.add('__round__') # set of magic methods MagicMock supports
是一套;当_magics
已经存在于该集合中时,添加__round__
是无害的,因此上述内容是面向未来的。另一种解决方法是模拟round()
内置函数,使用mock.patch()
在您的被测功能所在的模块中设置新的round
全局。
接下来,在测试时,您有3个选项:
通过设置呼叫的返回值来驱动测试,包括模拟以外的类型。例如,您可以设置模拟以返回.c()
调用的浮点值,这样您就可以断言您获得了正确的舍入结果:
>>> m.a.__add__.return_value.c.return_value = 42.12 # (m.a + ??).c() returns 42.12
>>> round((m.a + m.b).c()) == 42
True
断言已发生特定通话。有一个whole series of assert_call*
methods可以帮助您测试 调用,所有调用,特定顺序的调用等。还有一些属性,例如{{ 3}},.called
和.call_count
。请检查一下。
断言发生m.a + m.b
意味着断言以m.a.__add__
作为参数调用m.b
:
>>> m = mock.MagicMock()
>>> m.a + m.b
<MagicMock name='mock.a.__add__()' id='4500337776'>
>>> m.a.__add__.assert_called_with(m.b) # returns None, so success
如果要测试Mock
实例返回值,请遍历到预期的模拟对象,并使用is
测试标识:
>>> mock._magics.add('__round__')
>>> m = mock.MagicMock()
>>> r = round((m.a + m.b).c())
>>> mock_c_result = m.a.__add__.return_value.c.return_value
>>> r is mock_c_result.__round__.return_value
True
从来没有必要从模拟结果回到父母等等。只是穿过另一条路。
__add__
的lambda不起作用的原因是因为您使用参数创建了Mock()
实例。前两个参数是spec
和side_effect
参数。 spec
参数限制了模拟支持的属性,并且由于您将a
作为模拟对象规范传递而a
对象没有属性c
,因此您将获得一个属性c
上的错误。
答案 1 :(得分:0)
我自己找到了一个解决方案,但它并不是太漂亮了。忍受我。
正常的Mock
物体准备记录他们经历的许多治疗,但不是全部。 E. g。它们将在被调用时,在查询属性时以及更多内容时进行记录。但是,如果他们是e,他们不会记录(或接受)。 G。相互补充。添加被假定为&#34;魔术&#34;操作,使用&#34;魔术方法&#34; (__add__
)对象和Mock
不支持它们。
对于这些,还有另一个名为MagicMock
的类。 MagicMock
个对象支持魔术方法,因此添加它们适用于它们。结果将是另一个MagicMock
对象,可以询问它是如何创建的(通过添加另外两个MagicMock
对象)。
不幸的是,在当前版本(3.6.5)中,还没有包含魔术方法__round__
(在调用round(o)
时调用)。我猜他们只是忘了列出其他神奇方法,如__trunc__
,__floor__
,__ceil__
等。当我在源代码中添加它时,我也可以正确测试我的代码测试包括round()
电话。
但修补已安装的Python模块当然不是这样做的方法。由于它是当前实现中的缺陷,我希望将来能够修复,我目前的解决方案是仅在导入后更改mock
模块的内部数据结构。
我的测试现在的样子是:
def to_be_tested(x):
return round((x.a + x.b).c())
import unittest
import unittest.mock
# patch mock module's internal data structures to support round():
unittest.mock._all_magics.add('__round__')
unittest.mock._magics.add('__round__')
class Test_X(unittest.TestCase):
def test_x(self):
m = unittest.mock.MagicMock()
r = to_be_tested(m)
# now for the tests:
self.assertEqual(r._mock_new_name, '()') # created by calling
round_call = r._mock_new_parent
self.assertEqual(round_call._mock_new_name, '__round__')
c_result = round_call._mock_new_parent
self.assertEqual(c_result._mock_new_name, '()') # created by calling
c_call = c_result._mock_new_parent
self.assertEqual(c_call._mock_new_name, 'c')
add_result = c_call._mock_new_parent
self.assertEqual(add_result._mock_new_name, '()') # created by calling
add_call = add_result._mock_new_parent
self.assertEqual(add_call._mock_new_name, '__add__')
a_attribute = add_call._mock_new_parent
b_attribute = add_call.call_args[0][0]
self.assertEqual(a_attribute._mock_new_name, 'a')
self.assertEqual(b_attribute._mock_new_name, 'b')
self.assertIs(a_attribute._mock_new_parent, m)
self.assertIs(b_attribute._mock_new_parent, m)
Test_X().test_x()
像self.assertEqual(r, round((m.a + m.b).c()))
之类的简单测试遗憾地不够,因为它不会检查属性b
的名称(以及谁知道还有什么)。