使用python的模拟patch.object更改另一个方法内调用的方法的返回值


76

是否可以模拟我要测试的另一个函数中调用的函数的返回值?我希望模拟方法(在我正在测试的许多方法中都会被调用)在每次调用时都返回我指定的变量。例如:

class Foo:
    def method_1():
       results = uses_some_other_method()
    def method_n():
       results = uses_some_other_method()

在单元测试中,我想使用模拟来更改的返回值,uses_some_other_method()以便在每次调用时Foo,它将返回我定义的内容。@patch.object(...)


1
作为参考,任何人都可以参考这篇经过精心叙述的文章realpython.com/python-mock-library
Jinesh

Answers:


114

您可以通过两种方式执行此操作;与patch和patch.object

修补程序假定您不是直接导入对象,而是要测试的对象正在使用它,如下所示

#foo.py
def some_fn():
    return 'some_fn'

class Foo(object):
    def method_1(self):
        return some_fn()
#bar.py
import foo
class Bar(object):
    def method_2(self):
        tmp = foo.Foo()
        return tmp.method_1()
#test_case_1.py
import bar
from mock import patch

@patch('foo.some_fn')
def test_bar(mock_some_fn):
    mock_some_fn.return_value = 'test-val-1'
    tmp = bar.Bar()
    assert tmp.method_2() == 'test-val-1'
    mock_some_fn.return_value = 'test-val-2'
    assert tmp.method_2() == 'test-val-2'

如果直接导入要测试的模块,则可以按如下方式使用patch.object:

#test_case_2.py
import foo
from mock import patch

@patch.object(foo, 'some_fn')
def test_foo(test_some_fn):
    test_some_fn.return_value = 'test-val-1'
    tmp = foo.Foo()
    assert tmp.method_1() == 'test-val-1'
    test_some_fn.return_value = 'test-val-2'
    assert tmp.method_1() == 'test-val-2'

在这两种情况下,测试功能完成后,some_fn都将被“取消模拟”。

编辑:为了模拟多个功能,只需向功能添加更多装饰器,并添加参数以接受额外的参数

@patch.object(foo, 'some_fn')
@patch.object(foo, 'other_fn')
def test_foo(test_other_fn, test_some_fn):
    ...

请注意,装饰器离函数定义越近,它在参数列表中的位置就越早。


2
感谢您对patch和patch.object之间的区别的解释。
克里斯蒂安·朗

2
如果foo是我无权访问的库,而我想模拟的是对method_1的调用该怎么办?
skytreader

1
很好的答案,非常有启发性,而且解释清楚。
乔什·菲舍尔

12
非常感谢您提供这句话:“装饰器离函数定义越近,它在参数列表中就越早。” 刚刚花了1个小时调试了这个程序……
Lana Nova

1
@LanaNova在这里也一样。我期望参数的顺序类似于patch.object列出它们的方式。
trendsetter37

17

可以使用以下方法完成此操作:

# foo.py
class Foo:
    def method_1():
        results = uses_some_other_method()


# testing.py
from mock import patch

@patch('Foo.uses_some_other_method', return_value="specific_value"):
def test_some_other_method(mock_some_other_method):
    foo = Foo()
    the_value = foo.method_1()
    assert the_value == "specific_value"

这是您可以阅读的资源:在错误的位置修补


非常感谢。您引用的文章确实很有帮助。

7

让我澄清一下您在说什么:您想Foo在一个测试用例中进行测试,该用例调用external方法uses_some_other_method。您要模拟返回值,而不是调用实际方法。

class Foo:
    def method_1():
       results = uses_some_other_method()
    def method_n():
       results = uses_some_other_method()

假设上面的代码在module中foo.pyuses_some_other_method定义了bar.py。这是单元测试:

import unittest
import mock

from foo import Foo


class TestFoo(unittest.TestCase):

    def setup(self):
        self.foo = Foo()

    @mock.patch('foo.uses_some_other_method')
    def test_method_1(self, mock_method):
        mock_method.return_value = 3
        self.foo.method_1(*args, **kwargs)

        mock_method.assert_called_with(*args, **kwargs)

如果您想在每次传递不同的参数时更改返回值,请mock提供side_effect


0

为了增加Silfheed的答案(这很有用),我需要修补有问题对象的多种方法。我发现这样做比较优雅:

给定以下功能进行测试,位于module.a_function.to_test.py

from some_other.module import SomeOtherClass

def add_results():
    my_object = SomeOtherClass('some_contextual_parameters')
    result_a = my_object.method_a()
    result_b = my_object.method_b()
    
    return result_a + result_b

要测试此功能(或类方法,没关系),可以SomeOtherClass通过patch.object()结合使用来修补类的多个方法sys.modules

@patch.object(sys.modules['module.a_function.to_test'], 'SomeOtherClass')
def test__should_add_results(self, mocked_other_class):
  mocked_other_class().method_a.return_value = 4
  mocked_other_class().method_b.return_value = 7

  self.assertEqual(add_results(), 11)

无论SomeOtherClass您需要打补丁的方法有多种,并且结果独立,此方法都有效。

同样,SomeOtherClass如果需要,可以使用相同的修补方法返回的实际实例:

@patch.object(sys.modules['module.a_function.to_test'], 'SomeOtherClass')
def test__should_add_results(self, mocked_other_class):
  other_class_instance = SomeOtherClass('some_controlled_parameters')
  mocked_other_class.return_value = other_class_instance 
  ...
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.