Python,模拟和包装方法,没有即时对象

JLD*_*iaz 16 unit-testing mocking python-3.x

我想模拟一个类和方法的方法wraps,以便实际调用它,但我可以检查传递给它的参数.我在几个地方(例如这里)看到通常的方法如下(适应我的观点):

from unittest import TestCase
from unittest.mock import patch


class Potato(object):
    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):
    spud = Potato()

    @patch.object(Potato, 'foo', wraps=spud.foo)
    def test_something(self, mock):
        forty_two = self.spud.foo(n=40)
        mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)
Run Code Online (Sandbox Code Playgroud)

但是,这会实例化该类Potato,以便将mock绑定到实例方法spud.foo.

我需要的是foo所有实例中Potato模拟该方法,并将它们包装在原始方法周围.即,我需要以下内容:

from unittest import TestCase
from unittest.mock import patch


class Potato(object):
    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):
    @patch.object(Potato, 'foo', wraps=Potato.foo)
    def test_something(self, mock):
        self.spud = Potato()
        forty_two = self.spud.foo(n=40)
        mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)
Run Code Online (Sandbox Code Playgroud)

这当然不起作用.我收到错误:

TypeError: foo() missing 1 required positional argument: 'self'
Run Code Online (Sandbox Code Playgroud)

但是如果wraps没有使用它,那么问题不在于模拟本身,而在于它调用包装函数的方式.例如,这有效(但当然我必须"伪造"返回的值,因为现在Potato.foo从未实际运行):

from unittest import TestCase
from unittest.mock import patch


class Potato(object):
    def foo(self, n):
        return self.bar(n)

    def bar(self, n):
        return n + 2


class PotatoTest(TestCase):
    @patch.object(Potato, 'foo', return_value=42)#, wraps=Potato.foo)
    def test_something(self, mock):
        self.spud = Potato()
        forty_two = self.spud.foo(n=40)
        mock.assert_called_once_with(n=40)
        self.assertEqual(forty_two, 42)
Run Code Online (Sandbox Code Playgroud)

这是有效的,但它不运行我需要运行的原始函数,因为返回值在别处使用(我不能在测试中伪造它).

可以吗?

注意我需要的实际原因是我正在使用webtest测试rest api.从测试中,我对一些路径执行一些wsgi请求,我的框架实例化一些类并使用它们的方法来完成请求.我想捕获发送到这些方法的参数,asserts以便在我的测试中对它们进行一些处理.

小智 6

简而言之,您无法Mock单独使用实例来做到这一点。

patch.objectMock为指定实例(Potato)创建's,即Potato.foo在调用时用单个 Mock 替换。因此,无法将实例传递给,因为Mock模拟是在任何实例之前创建的。据我所知,在Mock运行时获取实例信息也非常困难。

为了显示:

from unittest.mock import MagicMock

class MyMock(MagicMock):
    def __init__(self, *a, **kw):
        super(MyMock, self).__init__(*a, **kw)
        print('Created Mock instance a={}, kw={}'.format(a,kw))

with patch.object(Potato, 'foo', new_callable=MyMock, wrap=Potato.foo):
    print('no instances created')
    spud = Potato()
    print('instance created')
Run Code Online (Sandbox Code Playgroud)

输出是:

Created Mock instance a=(), kw={'name': 'foo', 'wrap': <function Potato.foo at 0x7f5d9bfddea0>}
no instances created
instance created
Run Code Online (Sandbox Code Playgroud)

我建议对您的类进行猴子修补,以便将其添加Mock到正确的位置。

from unittest.mock import MagicMock

class PotatoTest(TestCase):
    def test_something(self):

        old_foo = Potato.foo
        try:
            mock = MagicMock(wraps=Potato.foo, return_value=42)
            Potato.foo = lambda *a,**kw: mock(*a, **kw)

            self.spud = Potato()
            forty_two = self.spud.foo(n=40)
            mock.assert_called_once_with(self.spud, n=40) # Now needs self instance
            self.assertEqual(forty_two, 42)
        finally:
            Potato.foo = old_foo
Run Code Online (Sandbox Code Playgroud)

请注意,当您使用called_with实例调用函数时,您的使用是有问题的。


900*_*000 0

您是否控制Potato实例的创建,或者至少在创建实例后有权访问这些实例?你应该这样做,否则你将无法检查特定的参数列表。如果是这样,您可以使用以下方法包装各个实例的方法

spud = dig_out_a_potato()
with mock.patch.object(spud, "foo", wraps=spud.foo) as mock_spud:
  # do your thing.
  mock_spud.assert_called...
Run Code Online (Sandbox Code Playgroud)