【问题标题】:Mocking 3rd party function in another module with python, pytest, mock使用 python、pytest、mock 在另一个模块中模拟第 3 方函数
【发布时间】:2018-12-21 01:21:16
【问题描述】:

我有一个正在生产中且无法修改的函数 func1()。它调用另一个模块中的函数function_to_be_mocked()。这需要输入参数。

我有另一个函数 func2() 调用 func1()。

我正在编写单元测试来测试 func2(),并尝试模拟 function_to_be_mocked(因为它取决于我在本地系统上没有(也不应该拥有)的一些键)。我唯一可以修改的是 test_func2()。

我的设置如下(最小示例):

from othermodule import function_to_be_mocked
import pytest
import mock

def func1():
    function_to_be_mocked(None)

def func2():
    ret = func1()
    print (ret)

@mock.patch('othermodule.function_to_be_mocked', return_value = 3)
def test_func2(mocker):
    func2()

而其他module.py是:

def function_to_be_mocked(arg1):
    if not arg1 == 'foo':
        raise ValueError

我的输出:

直接调用func2:

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/blah/temp.py", line 9, in func2
    ret = func1()
  File "/Users/blah/temp.py", line 6, in func1
    function_to_be_mocked(None)
  File "/Users/blah/othermodule.py", line 3, in function_to_be_mocked
    raise ValueError
ValueError

调用我希望被嘲笑的 test_func2():

Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/blah/venv/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "/Users/blah/temp.py", line 14, in test_func2
    func2()
  File "/Users/blah/temp.py", line 9, in func2
    ret = func1()
  File "/Users/blah/temp.py", line 6, in func1
    function_to_be_mocked(None)
  File "/Users/blah/othermodule.py", line 3, in function_to_be_mocked
    raise ValueError
ValueError

所以模拟似乎不起作用。有没有人有任何想法如何实现这一目标?

============ 在此行下方编辑 ===========

听起来我不能做我认为我能做的事情(因为我实际上无法修改与功能 1 或 2 相关的任何内容。我只能控制测试。

那么让我提出以下问题,因为也许比我更有经验的眼睛能看到前进的方向。

我有一个函数:

def function_to_be_tested(args):
    # Some processing steps

    # Function call that works locally
    function_that_returns_something_1()

    # Some logic

    # Function call that works locally
    function_that_returns_something_2()

    # Function that raises an exception when running locally,
    # and since I need to test the logic after this function 
    # (and cannot edit this code here to bypass it) I would 
    # (naively) like to mock it.
    function_I_would_like_to_mock()

    # Much more logic that follows this function. 
    # And this logic needs to be unit tested.
    return some_value_based_on_the_logic

测试:

def test_function_to_be_tested():
    assert function_to_be_tested(args) == some_expected_value

我可以在 function_I_would_like_to_mock() 之前轻松地对任何东西进行单元测试。

但是由于这个函数在本地崩溃(而且我无法编辑代码来阻止它在本地崩溃),我觉得正确的方法是模拟它并强制一个合理的返回值。这样我就可以对除此之外的代码路径进行单元测试。

您认为什么是好的方法?

请注意,我唯一可以修改的是测试功能。我什至无法在主函数中添加装饰器。

【问题讨论】:

    标签: python unit-testing mocking pytest


    【解决方案1】:

    选项 A)

    你愿意模拟的函数被加载到 func1 中。因此,您必须将 @patch 装饰器应用于 func1

    import pytest
    from unittest import mock
    
    @mock.patch('othermodule.function_to_be_mocked', return_value = 3)
    def func1(mocker):
        from othermodule import function_to_be_mocked
        function_to_be_mocked(None)
    
    def func2():
        ret = func1()
        print (ret)
    
    def test_func2():
        func2()
    
    test_func2()
    

    =========编辑===========

    选项 B)

    import pytest
    from unittest import mock
    
    
    def func1():
        from othermodule import function_to_be_mocked
        function_to_be_mocked(None)
    
    def func2():
        ret = func1()
        print (ret)
    
    def test_func2():
        with mock.patch('othermodule.function_to_be_mocked', return_value = 3) as irrelevant:
            func2()
    
    test_func2()
    

    【讨论】:

    • 感谢 Pablo,但除了测试功能之外,我无法对任何内容进行任何修改。我添加了对我想要实现的目标的更广泛描述,也许这是不可能的。
    • 你好,我不明白你不能改变func1。如果您实际上是从测试脚本中的模块导入它,那将是有意义的,对吗?
    • 是的,它在另一个模块中。为了这篇文章的目的,我只是将所有内容都放在了一个非常简短的示例中。非常感谢您的帮助。
    • 最后一个问题。如果我将 import 语句“从 othermodule import function_to_be_mocked”移动到文件顶部而不是 func2() 内部,这将不再起作用。为什么会这样?有没有办法解决这个问题,就像在我的源代码中一样导入是在文件顶部完成的?谢谢
    • 没关系,我想通了。我需要修改要模拟的函数的路径,因为它已经导入到模块中,这就是我需要修补的那个。谢谢:)
    【解决方案2】:

    官方“unittest.mock — 模拟对象库”文档的Where to patch 部分非常清楚地解释了这一点:

    a.py
        -> Defines SomeClass
    
    b.py
        -> from a import SomeClass
        -> some_function instantiates SomeClass
    

    现在我们想测试 some_function 但我们想使用 patch() 模拟 SomeClass。问题是当我们导入模块 b 时,我们必须这样做,然后它会从模块 a 导入 SomeClass。如果我们使用 patch() 来模拟 a.SomeClass 那么它对我们的测试没有影响;模块 b 已经引用了真实的 SomeClass,看起来我们的补丁没有效果。

    关键是在使用它的地方(或查找它的地方)修补 SomeClass。在这种情况下, some_function 实际上会在模块 b 中查找 SomeClass,我们已经在其中导入了它。修补程序应如下所示:

    @patch('b.SomeClass')
    

    所以我认为在你的情况下,补丁应该是这样的:

    @patch("module_of_func2.function_to_be_mocked_as_it_is_imported_there", return_value=3)
    def test_func2():
        ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-16
      • 2011-07-14
      • 2016-08-17
      • 2019-11-07
      • 1970-01-01
      • 2019-12-15
      • 1970-01-01
      相关资源
      最近更新 更多