使用 python 的模拟 patch.object 更改在另一个方法中调用的方法的返回值
Posted
技术标签:
【中文标题】使用 python 的模拟 patch.object 更改在另一个方法中调用的方法的返回值【英文标题】:Using python's mock patch.object to change the return value of a method called within another method 【发布时间】:2013-08-14 00:25:10 【问题描述】:是否可以模拟在我尝试测试的另一个函数中调用的函数的返回值?我希望模拟方法(将在我正在测试的许多方法中调用)在每次调用时返回我指定的变量。例如:
class Foo:
def method_1():
results = uses_some_other_method()
def method_n():
results = uses_some_other_method()
在单元测试中,我想使用mock来改变uses_some_other_method()
的返回值,这样任何时候在Foo
中调用它都会返回我在@patch.object(...)
中定义的内容
【问题讨论】:
供参考,任何人都可以参考这篇讲得很好的文章realpython.com/python-mock-library 【参考方案1】:有两种方法可以做到这一点;带补丁和带补丁.object
Patch 假定您不是直接导入对象,而是您正在测试的对象正在使用它,如下所示
#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):
...
注意,装饰器越靠近函数定义,它在参数列表中的位置就越早。
【讨论】:
感谢您解释patch和patch.object的区别。 如果 foo 是一个我无权访问的库,而我想模拟的是对 method_1 的调用,该怎么办? 很好的答案,内容丰富且解释清楚。 非常感谢这句话:“装饰器越接近函数定义,它在参数列表中的位置就越早。”只花了 1 小时调试这个... @LanaNova 在这里相同。我希望参数的顺序类似于 patch.object 列出它们的方式。【参考方案2】:这可以通过以下方式完成:
# 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"
这是您可以阅读的来源:Patching in the wrong place
【讨论】:
非常感谢。你提到的文章真的很有帮助。【参考方案3】:让我澄清一下你在说什么:你想在一个测试用例中测试Foo
,它调用外部方法uses_some_other_method
。您不想调用实际的方法,而是要模拟返回值。
class Foo:
def method_1():
results = uses_some_other_method()
def method_n():
results = uses_some_other_method()
假设上述代码在foo.py
中,uses_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
。
【讨论】:
【参考方案4】:为了添加到 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
要测试这个函数(或者类方法,没关系),可以通过patch.object()
结合sys.modules
来修补SomeOtherClass
类的多个方法:
@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
...
【讨论】:
以上是关于使用 python 的模拟 patch.object 更改在另一个方法中调用的方法的返回值的主要内容,如果未能解决你的问题,请参考以下文章