我有这段代码在测试中:
def to_be_tested(x):
return round((x.a + x.b).c())
在我的 unittest 中,我想断言这是通过传递的x
和返回的结果完成的,所以我将一个MagicMock
对象作为x
:
class Test_X(unittest.TestCase):
def test_x(self):
m = unittest.mock.MagicMock()
r = to_be_tested(m)
然后我检查结果是否符合我的预期:
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)
导入unittest.mock
后,我需要修补mock
模块的内部结构,以便能够正确地魔术模拟round()
函数(有关详细信息,请参阅 https://stackoverflow.com/a/50329607/1281485(:
unittest.mock._all_magics.add('__round__')
unittest.mock._magics.add('__round__')
所以,现在,正如我所说,这行得通。 但我发现它非常难以阅读。 此外,我需要玩很多东西才能找到_mock_new_parent
等东西。 下划线还指示这是一个"私有"属性,不应使用。 文档没有提到它。 它也没有提到实现我尝试的另一种方式。
有没有更好的方法来测试返回的MagicMock
对象是否按应有的方式创建?
你太过分了。您正在测试实现,而不是结果。此外,您正在进入不需要接触的模拟实现的内部。
测试是否获得正确的结果,并测试结果是否基于要使用的输入。您可以设置模拟,以便将round()
传递一个实际的数值进行舍入:
x.a + x.b
导致对m.a.__add__
的调用,传入m.b
。m.a.__add__().c()
被调用,因此我们可以测试它是否被调用(如果需要(。- 只需将
c()
的结果设置为一个数字,round()
就可以四舍五入。 从函数中获取正确的round(number)
结果意味着调用了.c()
。
在这里,将数字传递给round()
就足够了,因为您没有测试round()
函数。你可以依靠Python维护者来测试这个功能,专注于测试你自己的代码。
这是我要测试的:
m = unittest.mock.MagicMock()
# set a return value for (x.a + *something*).c()
mock_c = m.a.__add__.return_value.c
mock_c.return_value = 42.4
r = to_be_tested(m)
mock_c.assert_called_once()
self.assertEqual(r, 42)
如果您必须断言发生了m.a + m.b
,则可以添加
m.a.__add__.assert_called_once(m.b)
但是mock_c
调用断言传递已经证明至少发生了(m.a + <whatever>)
表达式,并且c
在结果上被访问。
如果必须验证round()
是否在实际的模拟实例上使用,则必须坚持修补MagicMock
类以包含__round__
作为特殊方法并删除mock_c.return_value
赋值,之后您可以断言返回值是正确的对象
# assert that the result of the `.c()` call has been passed to the
# round() function (which returns the result of `.__round__()`).
self.assertIs(r, mock_c.return_value.__round__.return_value)
一些进一步的说明:
- 试图将所有内容都变成模拟对象是没有意义的。如果被测试的代码应该在标准 Python 类型上运行,只需让您的模拟生成这些类型即可。例如,如果某些调用预期会生成一个字符串,请让您的模拟返回一个测试字符串,尤其是当您将内容传递给其他标准库 API 时。
- 模拟是单例。您无需从给定的模拟中返回以测试它们是否具有正确的父对象,因为您可以通过遍历父属性然后使用
is
来访问同一对象。例如,如果一个函数在某处返回一个模拟对象,你可以通过测试assertIs(mock_object.some.access.return_value.path, returned_object)
断言返回了正确的模拟对象。 - 当调用模拟时,会记录该事实。您可以使用
assert_called*
方法、.called
和.call_count
属性断言这一点,并使用.return_value
属性遍历调用的结果 如有疑问,请检查
.mock_calls
属性以查看受测代码访问了哪些内容。或者在交互式会话中执行此操作。例如,使用以下方法更容易查看m.a + m.b
在快速测试中执行的操作:>>> from unittest import mock >>> m = mock.MagicMock() >>> m.a + m.b <MagicMock name='mock.a.__add__()' id='4495452648'> >>> m.mock_calls [call.a.__add__(<MagicMock name='mock.b' id='4495427568'>)]