如何在 pytest 中使用 monkeypatch 或 mock 删除库?
How to remove a library with monkeypatch or mock in pytest?
如果我的库有一个 contrib
extra,其中有依赖项(比如 requests
),我希望用户必须安装才能访问 CLI API,但是我在 CI 中的测试期间安装了 contrib extra 如何使用 pytest
's MonkeyPatch
在测试期间删除依赖项以确保我的检测正确?
例如,如果 contrib
extra 将额外安装 requests
,所以我希望用户必须执行
$ python -m pip install mylib[contrib]
然后能够在命令行上有一个 CLI API 看起来像
$ mylib contrib myfunction
其中 myfunction
使用 requests
依赖项
# mylib/src/mylib/cli/contrib.py
import click
try:
import requests
except ModuleNotFoundError:
pass # should probably warn though, but this is just an example
# ...
@click.group(name="contrib")
def cli():
"""
Contrib experimental operations.
"""
@cli.command()
@click.argument("example", default="-")
def myfunction(example):
requests.get(example)
# ...
如何在我的 pytest
测试中模拟或 monkeypatch out requests
以确保用户会正确地收到警告与 ModuleNotFoundError
如果他们只是
$ python -m pip install mylib
$ mylib contrib myfunction
?在阅读了有关 pytest 标签的其他一些问题后,我认为我仍然不明白该怎么做,所以我在这里问。
我最终所做的是有效的,我已经确认这是一个合理的方法 thanks to Anthony Sottile,是通过将其设置为来模拟额外的依赖项(此处 requests
)不存在None
在 sys.modules
中,然后重新加载需要使用 requests
的模块。
我测试有一个实际的投诉,即 requests
不存在要使用 caplog
.
导入
这是我目前正在使用的测试(名称已更改以匹配我在上述问题中的玩具示例问题)
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()
我应该注意到,这实际上是在 中提倡的,@hoefling 链接到上面(在我解决了这个问题之后但在我开始发布这个之前发布)。
如果人们有兴趣在实际的图书馆中看到这个,c.f。以下两个 PR:
注意事项:
Anthony Sottile 警告说
reload()
can be kinda iffy -- I'd be careful with it (things which have old references to the old module will live on, sometimes it can introduce new copies of singletons (doubletons? tripletons?)) -- I've tracked down many-a-test-pollution problems to reload()
所以如果我实施更安全的替代方案,我会修改这个答案。
如果我的库有一个 contrib
extra,其中有依赖项(比如 requests
),我希望用户必须安装才能访问 CLI API,但是我在 CI 中的测试期间安装了 contrib extra 如何使用 pytest
's MonkeyPatch
在测试期间删除依赖项以确保我的检测正确?
例如,如果 contrib
extra 将额外安装 requests
,所以我希望用户必须执行
$ python -m pip install mylib[contrib]
然后能够在命令行上有一个 CLI API 看起来像
$ mylib contrib myfunction
其中 myfunction
使用 requests
依赖项
# mylib/src/mylib/cli/contrib.py
import click
try:
import requests
except ModuleNotFoundError:
pass # should probably warn though, but this is just an example
# ...
@click.group(name="contrib")
def cli():
"""
Contrib experimental operations.
"""
@cli.command()
@click.argument("example", default="-")
def myfunction(example):
requests.get(example)
# ...
如何在我的 pytest
测试中模拟或 monkeypatch out requests
以确保用户会正确地收到警告与 ModuleNotFoundError
如果他们只是
$ python -m pip install mylib
$ mylib contrib myfunction
?在阅读了有关 pytest 标签的其他一些问题后,我认为我仍然不明白该怎么做,所以我在这里问。
我最终所做的是有效的,我已经确认这是一个合理的方法 thanks to Anthony Sottile,是通过将其设置为来模拟额外的依赖项(此处 requests
)不存在None
在 sys.modules
中,然后重新加载需要使用 requests
的模块。
我测试有一个实际的投诉,即 requests
不存在要使用 caplog
.
这是我目前正在使用的测试(名称已更改以匹配我在上述问题中的玩具示例问题)
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()
我应该注意到,这实际上是在
如果人们有兴趣在实际的图书馆中看到这个,c.f。以下两个 PR:
注意事项: Anthony Sottile 警告说
reload()
can be kinda iffy -- I'd be careful with it (things which have old references to the old module will live on, sometimes it can introduce new copies of singletons (doubletons? tripletons?)) -- I've tracked down many-a-test-pollution problems toreload()
所以如果我实施更安全的替代方案,我会修改这个答案。