正确检查 Python 单元测试中的 MagicMock 对象



我有这段代码在测试中:

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'>)]
    

相关内容

  • 没有找到相关文章

最新更新