在Python中添加Mock对象 [英] Adding Mock objects in Python
问题描述
我正在测试的代码确实是这样的:
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,而是要添加一个新创建的Mock.
我也尝试了(已经很麻烦了)m.a.__add__ = lambda a, b: unittest.mock.Mock(a, b)
.但这(令我惊讶)导致在调用受测代码时产生了AttributeError: Mock object has no attribute 'c'
.我不明白这是因为我在此处创建的Mock应该接受我在其中调用了c()
,对吧?
有没有一种方法可以实现我想要的?如何创建可以添加到另一个Mock的Mock?
或者像我一样,还有另一种标准的单元测试代码方法吗?
我不希望提供专门的代码来为预期的调用准备所传递的模拟.我只想在调用后通过检查传递和返回的模拟对象来检查是否一切都按预期进行.我认为这种方式应该可行,在这种情况(以及其他类似的复杂情况)下,我可以利用它.
添加对象需要这些对象至少实现__add__
,这是一种特殊的方法,被Mock称为魔术方法,请参见 模拟魔术方法部分文档中的a>:
由于对魔术方法的查找与正常方法不同,因此已特别实现了此支持.这意味着仅支持特定的魔术方法.支持的列表几乎包括所有这些.如果您有任何遗漏,请告诉我们.
访问mock
支持的魔术方法的最简单方法是,您可以创建获得通话结果Mock.return_value
属性:
>>> 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()
也调用了一种魔术方法,即 __round__()
方法一个>.不幸的是,这不在受支持的方法列表中:
>>> round(mock.MagicMock())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type MagicMock doesn't define __round__ method
这可能是一个疏忽,因为包括了其他数值方法,例如__trunc__
和__ceil__
.我提交了一个错误报告,要求添加该错误.您可以使用以下方法将其手动添加到MagicMock
支持的方法列表中:
mock._magics.add('__round__') # set of magic methods MagicMock supports
_magics
是一组;在该集合中已经存在的情况下添加__round__
是没有害处的,因此上述内容可以用于将来.另一种解决方法是使用round()内置函数="nofollow noreferrer"> 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
-
确认已发生特定的呼叫.有一个
整个 assert_call*
方法系列可以帮助您测试 a 调用,所有调用,按特定顺序进行的调用等.还有一些属性,例如.called
,mock_calls
.请检查一下.断言
m.a + m.b
发生意味着断言以m.b
作为参数调用m.a.__add__
:>>> 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
上会出现属性错误.>
My code under test does sth like this:
def to_be_tested(x):
return round((x.a + x.b).c())
I would like to test it by passing a Mock object as x. I tried to do it like this:
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
The call to x.a
and the one to x.b
work as expected. They deliver new mock objects which can be asked how they were created (e. g. via q._mock_parent
and q._mock_new_name
), so this step works just fine.
But then the addition is supposed to take place which just raises an error (TypeError: unsupported operand type(s) for +: 'Mock' and 'Mock'
). I hoped this also would return a mock object so that the call to .c()
can take place and (again) return a mock object.
I also considered m.__add__ = lambda a, b: unittest.mock.Mock(a, b)
prior to the call of the code under test, but that wouldn't help since not my original Mock is going to be added but a newly created one.
I also tried the (already quite cumbersome) m.a.__add__ = lambda a, b: unittest.mock.Mock(a, b)
. But that (to my surprise) led to raising an AttributeError: Mock object has no attribute 'c'
when calling the code under test. Which I do not understand because the Mock I create there should accept that I called c()
in it, right?
Is there a way I can achieve what I want? How can I create a Mock which is capable of being added to another Mock?
Or is there another standard way of unittesting code like mine?
EDIT: I'm not interested in providing a specialized code which prepares the passed mocks for the expected calls. I only want to check after the call that everything has been happening as expected by examining the passed and returned mock objects. I think this way is supposed to be possible and in this (and other complex cases like this) I could make use of it.
Adding objects requires those objects to at least implement __add__
, a special method, called magic methods by Mock, see the Mocking Magic Methods section in the documentation:
Because magic methods are looked up differently from normal methods, this support has been specially implemented. This means that only specific magic methods are supported. The supported list includes almost all of them. If there are any missing that you need please let us know.
The easiest way to get access to those magic methods that are supported by mock
, you can create an instance of the MagicMock
class, which provides default implementations for those (each returning a now MagicMock
instance by default).
This gives you access to the x.a + x.b
call:
>>> 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'>)]
A call to m.a.__add__()
has been recorded, with the argument being m.b
; this is something we can now assert in a test!
Next, that same m.a.__add__()
mock is then used to supply the .c()
mock:
>>> (m.a + m.b).c()
<MagicMock name='mock.a.__add__().c()' id='4500162544'>
Again, this is something we can assert. Note that if you repeat this call, you'll find that mocks are singletons; when accessing attributes or calling a mock, more mocks of the same type are created and stored, you can later use these stored objects to assert that the right object has been handed out; you can reach the result of a call with the Mock.return_value
attribute:
>>> 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
Now, on to round()
. round()
calls a magic method too, the __round__()
method. Unfortunately, this is not on the list of supported methods:
>>> round(mock.MagicMock())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: type MagicMock doesn't define __round__ method
This is probably an oversight, since other numeric methods such as __trunc__
and __ceil__
are included. I filed a bug report to request it to be added. You can manually add this to the MagicMock
supported methods list with:
mock._magics.add('__round__') # set of magic methods MagicMock supports
_magics
is a set; adding __round__
when it already exists in that set is harmless, so the above is future-proof. An alternative work-around is to mock the round()
built-in function, using mock.patch()
to set a new round
global in the module where your function-under-test is located.
Next, when testing, you have 3 options:
Drive tests by setting return values for calls, including types other than mocks. For example, you can set up your mock to return a floating point value for the
.c()
call, so you can assert that you get correctly rounded results:>>> m.a.__add__.return_value.c.return_value = 42.12 # (m.a + ??).c() returns 42.12 >>> round((m.a + m.b).c()) == 42 True
Assert that specific calls have taken place. There are a whole series of
assert_call*
methods that help you with testing for a call, all calls, calls in a specific order, etc. There are also attributes such as.called
,.call_count
, andmock_calls
. Do check those out.Asserting that
m.a + m.b
took place means asserting thatm.a.__add__
was called withm.b
as an argument:>>> 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
If you want to test a
Mock
instance return value, traverse to the expected mock object, and useis
to test for identity:>>> 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
There is never a need to go back from a mock result to parents, etc. Just traverse the other way.
The reason your lambda for __add__
doesn't work is because you created a Mock()
instance with arguments. The first two arguments are the spec
and the side_effect
arguments. The spec
argument limits what attributes a mock supports, and since you passed in a
as a mock object specification and that a
object has no attribute c
, you get an attribute error on c
.
这篇关于在Python中添加Mock对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!