【问题标题】:Mocking a Standard Library function with and without pytest-mock使用和不使用 pytest-mock 模拟标准库函数
【发布时间】:2016-11-04 17:06:49
【问题描述】:

出于测试目的,我想模拟 shutil.which (Python 3.5.1),它在简化方法 find_foo() 中调用

def _find_foo(self) -> Path:
foo_exe = which('foo', path=None)
if foo_exe:
    return Path(foo_exe)
else:
    return None

我正在使用 pytest 来实现我的测试用例。因此,我也想使用 pytest 扩展 pytest-mock。在下面,我使用 pytest + pytest-mock 粘贴了一个示例测试用例:

def test_find_foo(mocker):
    mocker.patch('shutil.which', return_value = '/path/foo.exe')

    foo_path = find_foo()
    assert foo_path is '/path/foo.exe'

这种用 pytest-mock 模拟的方式是行不通的。 shutil.which 仍然被调用而不是 mock。

我尝试直接使用现在是 Python3 一部分的 mock 包:

def test_find_foo():
    with unittest.mock.patch('shutil.which') as patched_which:
        patched_which.return_value = '/path/foo.exe'

        foo_path = find_foo()
        assert foo_path is '/path/foo.exe'

遗憾的是结果是一样的。还调用 shutil.which() 而不是指定的模拟。

在我的测试用例中成功实现模拟的哪些步骤是错误的或遗漏的?

【问题讨论】:

    标签: python unit-testing mocking pytest


    【解决方案1】:

    尝试使用monkeypatch。您可以在示例中看到他们如何“monkeypatch”os.getcwd 返回想要的路径。在您的情况下,我认为这应该可行:

     monkeypatch.setattr("shutil.which", lambda: "/path/foo.exe")
    

    【讨论】:

    • 我尝试使用 monkeypatch 方法并阅读了更多相关信息。首先对我来说,monkeypatch 看起来经常被用来替换有缺陷的代码,而不是在测试用例中进行典型的模拟。我还详细发现了一些问题,例如猴子补丁“shutil.which”和稍后调用“which”(由于“from shutil import which”而在命名空间中可用)不起作用。如果在我的生产代码中使用了快捷方式(没有“shutil.”),则不会调用 lamba 函数。 Acutally 我没有解决方案,但我仍然认为经典模拟是更好的解决方案。
    【解决方案2】:

    which 方法注入您的方法或对象将允许您在没有pytest-mock 的情况下模拟依赖项。

    def _find_foo(self, which_fn=shutil.which) -> Path:
      foo_exe = which_fn('foo', path=None)
      if foo_exe:
          return Path(foo_exe)
      else:
          return None
    
    
    def test_find_foo():
       mock_which = Mock(return_value = '/path/foo.exe')
    
       foo_path = obj._find_foo(which_fn=mock_which)
       assert foo_path is '/path/foo.exe'
    

    【讨论】:

    • 依赖注入也是模拟“which”函数的有效方法。尽管如此,对我来说,为了测试目的而调整我的生产代码是不可接受的。
    【解决方案3】:

    我花了更多时间研究 unittest.mock 和 pytest-mock。我找到了一个简单的解决方案,无需使用补丁装饰器修改生产代码。在下面我粘贴了一段代码 sn-p 演示了使用 pytest-mock 的第三种方法:

    def test_find_foo(mocker):
        mocker.patch('__main__.which', return_value='/path/foo.exe')
    
        foo_path = find_foo()
        assert foo_path == Path('/path/foo.exe')
    

    如果没有 pytest-mock(普通的 unittest-mock 和 @patch 装饰器),这个解决方案也可以工作。上面代码sn -p中重要的一行是

    mocker.patch('__main__.which', return_value='/path/foo.exe')
    

    补丁装饰器需要从被测系统调用的函数的名称(完整路径)。这在mock documentation 中有明确的解释。以下段落总结了补丁装饰器的这一原理:

    patch 通过(临时)将名称指向的对象更改为另一个对象。可以有许多名称指向任何单个对象,因此要使修补工作正常,您必须确保修补被测系统使用的名称。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-17
      • 2011-07-14
      • 1970-01-01
      • 2018-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多