简体   繁体   English

在有和没有pytest-mock的情况下模拟标准库函数

[英]Mocking a Standard Library function with and without pytest-mock

For testing purposes I would like to mock shutil.which (Python 3.5.1), which is called inside a simplified method find_foo() 为了进行测试,我想模拟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

I'm using pytest for implementing my test cases. 我正在使用pytest来实现我的测试用例。 Because of that I also would like to use the pytest extension pytest-mock. 因此,我也想使用pytest扩展名pytest-mock。 In the following I pasted an example testcase using 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'

This way of mocking with pytest-mock doesn't work. 用pytest-mock模拟的这种方式不起作用。 shutil.which is still called instead of the mock. shutil。仍然被调用而不是模拟

I tried to directly use the mock package which is now part of Python3: 我试图直接使用现在是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'

Sadly the result is the same. 可悲的是结果是一样的。 Also shutil.which() is called instead of specified mock. 还会shutil.which()而不是指定的模拟。

Which steps of successfully implementing a mock are wrong or missed in my test cases? 在我的测试案例中,成功实现模拟的哪些步骤是错误的或错过了?

I investigated more time studying unittest.mock and pytest-mock. 我花了更多时间研究unittest.mock和pytest-mock。 I found a simple solution without modifying the production code using the patch decorator. 我找到了一个简单的解决方案,而没有使用补丁装饰器修改生产代码。 In the following I pasted a code snippet demonstrating a third approach with 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')

Without pytest-mock (plain unittest-mock and a @patch decorator) this solution is also working. 如果没有pytest-mock(普通的unittest-mock和@patch装饰器),此解决方案也可以工作。 The important line in the code snippet above is 上面的代码片段中的重要一行是

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

The patch decorator expects the name (full path) of the function which will be called from the system under test. 修补程序装饰程序需要从测试中的系统调用的功能名称 (完整路径)。 This is clearly explained in the mock documentation . 模拟文档中对此进行了明确说明。 The following paragraph summarizes this principle of the patch decorator: 以下段落总结了补丁装饰器的这一原理:

patch works by (temporarily) changing the object that a name points to with another one. 修补程序通过(临时)将名称指向的对象更改为另一个对象来工作。 There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test. 可以有许多名称指向任何单个对象,因此要使修补程序起作用,必须确保修补受测系统使用的名称。

Try using monkeypatch . 尝试使用monkeypatch You can see in the examples how they "monkeypatch" os.getcwd to return the wanted path. 您可以在示例中看到它们如何“ monkeypatch” os.getcwd返回所需的路径。 In your case I think that this should work: 在您的情况下,我认为这应该可行:

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

Injecting the which method into your method or object would allow you to mock the dependency without pytest-mock . which方法注入到您的方法或对象中将使您可以在没有pytest-mock情况下模拟依赖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'

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM