如何在pytest中使用monkeypatch或mock删除库?

Mat*_*ert 6 python monkeypatching pytest pytest-mock

如果我的库中有一个contrib附加程序,其中包含依赖项(例如requests),我希望用户必须安装该附加程序才能访问 CLI API,但我在 CI 测试期间安装了 contrib 附加程序,我如何使用pytestMonkeyPatch删除测试期间的依赖关系以确保我的检测正确?

例如,如果contrib额外的内容会另外安装requests,那么我希望用户必须这样做

$ python -m pip install mylib[contrib]
Run Code Online (Sandbox Code Playgroud)

然后能够在命令行上使用 CLI API,如下所示

$ mylib contrib myfunction
Run Code Online (Sandbox Code Playgroud)

wheremyfunction使用requests依赖项

$ python -m pip install mylib[contrib]
Run Code Online (Sandbox Code Playgroud)

我如何在测试中模拟或猴子补丁 ,以便我可以确保用户正确地收到警告以及如果他们这样做requestspytestModuleNotFoundError

$ python -m pip install mylib
$ mylib contrib myfunction
Run Code Online (Sandbox Code Playgroud)

?在阅读了有关 pytest 标签的其他一些问题后,我仍然认为我不明白如何做到这一点,所以我在这里问。

Mat*_*ert 4

我最终所做的工作是有效的,并且我已经确认这是一个合理的方法,感谢 Anthony Sottile,就是通过将其设置为in然后重新加载模块来模拟额外的依赖项(此处requests)不存在。需要使用. 我测试了是否有一个实际的投诉不存在可以通过使用导入。Nonesys.modulesrequestsrequestscaplog

这是我当前正在使用的测试(名称已更改以匹配上面问题中的玩具示例问题)

import mylib
import sys
import logging
import pytest
from unittest import mock
from importlib import reload
from importlib import import_module

# ...

def test_missing_contrib_extra(caplog):
    with mock.patch.dict(sys.modules):
        sys.modules["requests"] = None
        if "mylib.contrib.utils" in sys.modules:
            reload(sys.modules["mylib.contrib.utils"])
        else:
            import_module("mylib.cli")

    with caplog.at_level(logging.ERROR):
        # The 2nd and 3rd lines check for an error message that mylib throws
        for line in [
            "import of requests halted; None in sys.modules",
            "Installation of the contrib extra is required to use mylib.contrib.utils.download",
            "Please install with: python -m pip install mylib[contrib]",
        ]:
            assert line in caplog.text
        caplog.clear()
Run Code Online (Sandbox Code Playgroud)

我应该注意到,这实际上是在@Abhyudai 对“使用 pytest 在init .py 中导入可选依赖项进行测试:Python 3.5 /3.6 行为不同”的回答中提倡的,@hoefling 链接到上面(在我解决了这个问题之后发布,但是在我开始发布这个之前)。

如果人们有兴趣在实际的图书馆中看到这一点,请参阅以下两个 PR:

注:安东尼·索蒂尔警告说

reload()可能有点不确定 - 我会小心它(对旧模块有旧引用的东西将继续存在,有时它可以引入单例的新副本(双例?三例?)) - 我已经追踪到了很多-a-测试污染问题reload()

所以如果我实施一个更安全的替代方案,我会修改这个答案。

  • 从这个 Twitter 帖子中,安东尼会说不:https://twitter.com/codewithanthony/status/1338547566607101952 “我个人的意见是永远不要使用 Monkeypatch 固定装置,因为模拟范围没有很好地定义/控制” (2认同)