在Python中添加Mock对象 [英] Adding Mock objects in Python

查看:89
本文介绍了在Python中添加Mock对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在测试的代码确实是这样的:

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_parentq._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称为魔术方法,请参见 模拟魔术方法部分:

由于对魔术方法的查找与正常方法不同,因此已特别实现了此支持.这意味着仅支持特定的魔术方法.支持的列表几乎包括所有这些.如果您有任何遗漏,请告诉我们.

访问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 解决方案

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, and mock_calls. Do check those out.

    Asserting that m.a + m.b took place means asserting that m.a.__add__ was called with m.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 use is 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆