pytest06 MonkeyPatching/Mocking module/environment
这个 monkeypatch fixture为测试中的安全修补和模拟功能提供了以下帮助方法:
1 | monkeypatch.setattr(obj, name, value, raising=True) |
所有修改将在请求的测试功能或固件完成后撤消。如果设置/删除操作的目标不存在,这个 raising 参数决定了是否抛出 KeyError 或 AttributeError错误。
考虑以下情况:
为测试修改函数的行为或类的属性,例如有一个API调用或数据库连接,你将无法进行测试,但你知道预期的输出应该是什么。使用
monkeypatch.setattr()使用所需的测试行为修补函数或属性,这可以包括你自己的功能,使用monkeypatch.delattr()删除测试的函数或属性。修改字典的值,例如,对于某些测试用例,你需要修改全局配置。使用
monkeypatch.setitem()为测试修改字典。monkeypatch.delitem()可用于删除项目。修改测试的环境变量,例如在缺少环境变量时测试程序行为,或将多个值设置为已知变量。
monkeypatch.setenv()和monkeypatch.delenv()可用于这些修补。使用
monkeypatch.setenv("PATH", value, prepend=os.pathsep)修改系统$PATH安全,以及monkeypatch.chdir()在测试期间更改当前工作目录的上下文。使用py:meth:
monkeypatch.syspath_prepend来修改sys.path,这将会调用pkg_resources.fixup_namespace_packages()` and `importlib.invalidate_caches()
简单示例:monkeypatching 函数
考虑使用用户目录的场景,在测试环境中,你不希望测试依赖于正在运行的用户。 monkeypatch 可用于修补依赖于用户的函数,以始终返回特定值。
在这个例子中, monkeypatch.setattr() 用于修补 Path.home 使已知的测试路径 Path("/abc") 总是在运行测试时使用。这将删除出于测试目的对正在运行的用户的任何依赖。 monkeypatch.setattr() 必须在调用将使用修补函数的函数之前调用。测试功能完成后, Path.home 修改将被撤消。
1 | # contents of test_module.py with source code and the test |
定义 mockreturn 函数:
1 | def mockreturn(): |
mockreturn是一个模拟函数,用于替换Path.home()的行为,使其始终返回Path("/abc"),即一个虚拟的路径/abc。
使用 monkeypatch 替换 Path.home 方法:
1 | monkeypatch.setattr(Path, "home", mockreturn) |
monkeypatch.setattr用于在测试期间临时替换Path.home方法,使其行为变为mockreturn。- 这样,在
test_getssh测试中,调用Path.home()就会返回/abc,而不是实际的主目录路径。
调用 getssh 并断言结果:
1 | x = getssh() |
- 调用
getssh()时,由于Path.home()已被替换为mockreturn,所以getssh会返回Path("/abc") / ".ssh",即Path("/abc/.ssh")。 assert语句用于验证getssh()的返回值是否为Path("/abc/.ssh")。如果返回值匹配,则测试通过;否则测试失败。
MonkeyPatching返回的对象:构建模拟类
monkeypatch.setattr() 可以与类一起使用,模拟从函数而不是值返回的对象。设想一个简单的函数获取一个API URL并返回JSON响应。我们需要模拟r ,返回的响应对象用于测试目的。 r 需要一个 .json() 返回字典的方法。这可以在我们的测试文件中通过定义一个类来表示 r .
1 | # app.py |
1 | # test_mock_response.py |
monkeypatch 将 mock_get 功能模拟应用于 requests.get ,这个 mock_get 函数返回 MockResponse 类,其中有一个 json() 方法定义为返回已知的测试字典,不需要任何外部API连接, 调用 app.get_json("https://fakeurl") 时,不会实际发送请求。
可以建立 MockResponse 为你正在测试的场景使用适当的复杂性来初始化。例如,它可以包括始终返回 True ok属性 或者基于输入字符串返回不同的值 json() 的模拟方法。
小结,使用 MockResponse 模拟返回对象:通过自定义 MockResponse 类模拟 HTTP 响应,以控制测试环境中的数据。**monkeypatch 替换 requests.get**:通过 monkeypatch 将 requests.get 替换为 mock_get,实现对外部依赖的隔离。
此外,如果模拟模型设计用于所有测试,则 fixture 可以移动到 conftest.py 归档并与一起使用 autouse=True 选择项。
全局补丁示例:防止远程操作的“请求”
如果要阻止“请求”库在所有测试中执行HTTP请求,可以执行以下操作:
1 | # contents of conftest.py |
将为每个测试功能执行该autouse fixture,并将删除该方法 request.session.Session.request 。因此,测试中创建HTTP请求的任何尝试都将失败。
建议不要修补内置函数,例如
open,compile等等,因为它可能会破坏pytest的内部。如果那是不可避免的,传递参数--tb=native,--assert=plain和--capture=no可能会有帮助,尽管没有保证。
修补 Python 内置函数(如 open、compile 等)或 Pytest 依赖的函数可能会导致 Pytest 及其功能(如断言和捕获输出)异常。因为 Pytest 本身依赖于这些函数,过度修补会干扰其正常运行。如果必须修补,可以尝试通过添加 --tb=native、--assert=plain 和 --capture=no 参数来减轻影响,不过这些参数并不总能保证避免问题。
注意修补
stdlib函数和 pytest 使用的函数和一些第三方库可能会破坏pytest本身,因此在这些情况下,建议使用MonkeyPatch.context()将修补限制到要测试的块,请执行以下操作:
1 | import functools |
使用 monkeypatch.context() 可以将修补作用域限制在 with 语句内的代码块,确保修补仅在该代码块内有效,代码块结束后修补会自动撤销。
这是对标准库函数、Pytest 依赖的函数或第三方库函数进行修补的更安全方式,因为它最小化了修补的作用范围,降低了对 Pytest 或系统行为的干扰风险。
MonkeyPatching 环境变量
如果你正在使用环境变量,为了测试的目的你需要安全地更改这些值或从系统中删除它们, monkeypatch 提供了一种使用 setenv 和 delenv 机制,我们要测试的示例代码:
1 | import os |
有两条可能的路径。首先, USER 环境变量设置为值,其次, USER 环境变量不存在。使用 monkeypatch 两条路径都可以在不影响运行环境的情况下进行安全测试:
1 | import pytest |
此行为可以移入 fixture 结构和跨测试共享:
1 | # test_monkeypatching_env.py |
MonkeyPatching 字典
monkeypatch.setitem() 可用于在测试期间安全地将字典值设置为特定值,使用 monkeypatch.delitem() 删除值。
1 | # app_dict.py |
1 | # test_monkeypatchDict_fixture.py |