python - 在 Python 单元测试中正确检查 MagicMock 对象
问题描述
我正在测试这段代码:
def to_be_tested(x):
return round((x.a + x.b).c())
在我的单元测试中,我想断言这正是通过传递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()
function。您可以依靠 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
. 例如,如果一个函数在某处返回一个模拟对象,您可以断言正确的模拟对象是由 testing 返回的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'>)]
推荐阅读
- computer-vision - 姿态估计中的groundtruths(热图和PAF)是如何生成的?
- c# - 使用 SQL 语句将多个参数传递给 tableadapter
- swift - @Published 类没有成员 .store | 结合 SwiftUI
- mysql - 如何启用对在线存储的节点应用程序的多用户访问?
- azure - 如何获取 Azure APIM 健康检查和详细信息
- reactjs - 从 Firebase 获取数据而不在 React Native 中获取 uid 键
- foreach - SSIS在foreach循环中排除多个项目
- php - CakePHP 3:表单控件不显示其默认值或值
- postgresql - 在 Postgress 中使用 Pattern 将字符串拆分为新行
- ruby-on-rails - Rails:如何在部署 Docker 容器后立即启动 PORO/job/worker?