Pytest monkeypatch无法处理导入的函数

Glu*_*eon 21 python unit-testing pytest

假设项目中有两个包:some_packageanother_package.

# some_package/foo.py:
def bar():
    print('hello')
Run Code Online (Sandbox Code Playgroud)
# another_package/function.py
from some_package.foo import bar

def call_bar():
    # ... code ...
    bar()
    # ... code ...
Run Code Online (Sandbox Code Playgroud)

我想测试another_package.function.call_bar模拟,some_package.foo.bar因为它有一些I/OI希望避免的网络.

这是一个测试:

# tests/test_bar.py
from another_package.function import call_bar

def test_bar(monkeypatch):
    monkeypatch.setattr('some_package.foo.bar', lambda: print('patched'))
    call_bar()
    assert True
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是它输出hello而不是patched.我尝试调试这个东西,在测试中放置一个IPDB断点.当我some_package.foo.bar在断点后手动导入并调用bar()我得到patched.

在我的真实项目中,情况更加有趣.如果我在项目根目录中调用pytest,我的函数不会被修补,但是当我指定tests/test_bar.py为参数时 - 它可以工作.

据我所知,它与from some_package.foo import bar声明有关.如果在monkeypatching发生之前执行它,那么修补失败.但是在上面的例子中的压缩测试设置中,修补在两种情况下都不起作用.

为什么在遇到断点后它在IPDB REPL中工作?

Ale*_*lex 29

虽然Ronny的回答有效,但它迫使您更改应用程序代码.一般来说,为了测试,你不应该这样做.

相反,您可以显式修补第二个包中的对象.这在unittest模块文档中提到.

monkeypatch.setattr('another_package.bar', lambda: print('patched'))
Run Code Online (Sandbox Code Playgroud)

  • 这显然是monkeypatching导入的更简洁的方式. (6认同)

ala*_*ock 16

正如亚历克斯所说,你不应该为你的测试重写你的代码。我遇到的问题是要修补的路径。

鉴于代码:

应用程序/处理程序/tasks.py

from auth.service import check_user

def handle_tasks_create(request):
  check_user(request.get('user_id'))
  create_task(request.body)
  return {'status': 'success'}
Run Code Online (Sandbox Code Playgroud)

你对monkeypatch 的第一直觉check_user,像这样:

monkeypatch.setattr('auth.service.check_user', lambda x: return None)
Run Code Online (Sandbox Code Playgroud)

但是您要做的是在tasks.py. 可能这就是你想要的:

monkeypatch.setattr('app.handlers.tasks.check_user', lambda x: return None)
Run Code Online (Sandbox Code Playgroud)

虽然给出的答案已经很好,但我希望这能带来更完整的背景。

  • `check_user` 可能在许多文件中使用。你是说我们每次都需要修补不同的路径吗? (2认同)
  • 经过一个小时的搜索后,这是一个救星。谢谢你! (2认同)
  • 我认为这是与@Alex 重复的答案,但仍然给了你一个赞成票,所以这个解释让它在我的脑海中更好一点,谢谢! (2认同)

Ron*_*nny 9

命名导入会为对象创建新名称.如果您随后替换对象的旧名称,则新名称不受影响.

导入模块并module.bar改为使用.这将始终使用当前对象.


编辑:

import module 

def func_under_test():
  module.foo()

def test_func():
   monkeypatch.setattr(...)
   func_under_test
Run Code Online (Sandbox Code Playgroud)

  • 这是pytest最糟糕的陷阱之一 - 但感谢您的解释. (6认同)
  • 当你说“使用module.bar”时,你能提供一个代码示例吗?我已经尝试过 `monkeypatch.setattr(module, 'bar', mock_obj)` 和其他一些咒语,但没有成功。 (6认同)
  • 这是python语言的属性,没有技巧 (3认同)