使用和不使用pytest-mock模拟标准库函数

时间:2016-07-02 11:44:45

标签: python unit-testing mocking pytest

出于测试目的,我想模拟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的一部分:

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()而不是指定mock。

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

3 个答案:

答案 0 :(得分:1)

尝试使用monkeypatch。你可以在例子中看到他们" monkeypatch" os.getcwd返回想要的路径。在你的情况下,我认为这应该有效:

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

答案 1 :(得分:1)

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'

答案 2 :(得分:1)

我研究了更多时间学习unittest.mock和pytest-mock。我找到了一个简单的解决方案,而没有使用补丁装饰器修改生产代码。在下面我粘贴了一个代码片段,演示了使用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(plain unittest-mock和@patch装饰器),这个解决方案也可以工作。上面代码段中的重要一行是

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

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

  

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