本文主要是介绍pytest之Monkeypatching(猴子补丁),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
猴子补丁(monkey patching)理解
在运行时动态修改模块、类或函数,通常是添加功能或修正缺陷。
猴子补丁在代码运行时(内存中)发挥作用,不会修改源码,因此只对当前运行的程序实例有效。
因为猴子补丁破坏了封装,而且容易导致程序与补丁代码的实现细节紧密耦合,所以被视为临时的变通方案,不是集成代码的推荐方式。
在Python语言中,monkey patch 指的是对于一个类或者模块所进行的动态修改。在Python语言中,我们其实可以在运行时修改代码的行为。
monkeypatch使用场景
该monkeypatch固定装置提供了这些帮助程序方法,用于安全地修补和模拟测试中的功能:
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
考虑以下情形:
1.修改测试的函数行为或类的属性,例如,您不会为测试创建API调用或数据库连接,但是您知道预期的输出结果。用于使用monkeypatch.setattr()所需的测试行为来修补功能或属性。这可以包括您自己的功能。使用monkeypatch.delattr()删除用于测试的功能或属性。
2.修改字典的值,例如,您具有要针对某些测试用例进行修改的全局配置。使用monkeypatch.setitem()修补字典进行测试。monkeypatch.delitem()可用于删除项目。
3.修改测试的环境变量,例如在缺少环境变量的情况下测试程序的行为,或为已知变量设置多个值。 monkeypatch.setenv()并monkeypatch.delenv()可以用于这些补丁。
4. 在测试期间,用于修改和 更改当前工作目录的上下文。monkeypatch.setenv(“PATH”, value, prepend=os.pathsep)$PATHmonkeypatch.chdir()
5.使用monkeypatch.syspath_prepend()修改sys.path哪个也会调用pkg_resources.fixup_namespace_packages()和importlib.invalidate_caches()。
monkeypatch类的方法
monkeypatch夹具返回的对象记录了setattr / item / env / syspath的更改。
通过context() 返回一个新MonkeyPatch对象的上下文管理器,该对象with在退出时撤消在块内完成的所有修补操作:
import functools
def test_partial(monkeypatch):with monkeypatch.context() as m:m.setattr(functools, "partial", 3)
在需要在测试结束前撤消某些补丁的情况下很有用,例如stdlib模拟功能,如果被模拟可能会破坏pytest本身
一,设置属性
setattr(target,name,value = ,raising = True )
在目标上设置属性值,记住旧值。默认情况下,如果属性不存在,请引发AttributeError。
该raising值确定如果属性不存在(默认为True,这意味着它将raising )。
为方便起见,您可以指定一个字符串,target该字符串将被解释为点分导入路径,最后一部分是属性名称。
示例: 将设置模块的功能。monkeypatch.setattr(“os.getcwd”, lambda: “/”)getcwdos
delattr(target,name = ,raising = True )
name从中删除属性target,默认情况下,如果属性以前不存在,则引发AttributeError。
如果raising设置为False,则缺少该属性将不会引发异常。
如果未name指定no 并且target它是一个字符串,它将被解释为带点的导入路径,最后一部分是属性名称。
二,设置字典
setitem(dic,name,value )
将字典条目设置name为值。
delitem(dic,name,raising = True )
name从字典中删除。如果它不存在,请引发KeyError。
如果raising设置为False,则如果缺少密钥,则不会引发异常。
三,设置环境
setenv(name,value,prepend = None )
将环境变量设置name为value。如果prepend 是字符,则读取当前环境变量值,并在value相邻的prepend字符前加上该字符。
delenv(name,raising= True )
name从环境中删除。如果它不存在,请引发KeyError。
如果raising设置为False,则缺少环境变量时不会引发异常。
四,其他
syspath_prepend(路径)
前置path到sys.path导入位置列表。
chdir(路径)
将当前工作目录更改为指定的路径。路径可以是字符串或py.path.local对象。
undo()
撤消之前的更改。该调用消耗了撤消堆栈。除非您在撤消调用之后进行更多的猴子修补,否则再次调用它没有任何作用。
通常不需要调用undo(),因为在拆卸过程中会自动调用它。
monkeypatch构建模拟类替代请求接口返回的对象
monkeypatch.setattr()可以与类结合使用,以模拟从函数而不是值返回的对象。想象一个简单的函数来获取API URL并返回json响应。
import requestsdef get_json(url):"""请求一个接口,返回json对象"""r = requests.get(url)return r.json()
r为了测试目的,我们需要模拟返回的响应对象。模拟r需要一个.json()返回字典的方法。可以通过定义一个class来表示在我们的测试文件中r。
class MockResponse:'''定义一个mock类和方法,返回固定json对象'''@staticmethoddef json():return {"mock_key": "mock_response"}def test_get_json(monkeypatch):''' 定义一个mock_get方法,无论传入什么参数,都返回上面的mock类,然后使用monkeypatch.setattr对requests.get重新设置,这样requests.get有了属性 “mock_get”,再次使用时就直接使用了mock_get方法'''def mock_get(*args, **kwargs):return MockResponse()monkeypatch.setattr(requests, "get", mock_get)result = get_json("https://fakeurl")assert result["mock_key"] == "mock_response_1"
执行用例成功:
monkeypatch将模拟requests.get与我们的mock_get函数一起应用。
该mock_get函数返回MockResponse该类的实例,该类具有json()定义为返回已知测试字典的方法,并且不需要任何外部API连接。可以MockResponse针对要测试的场景以适当的复杂度构建类。
使用fixture:
import pytest
import requestsclass MockResponse:@staticmethoddef json():return {"mock_key": "mock_response"}# monkeypatch操作移到fixture中
@pytest.fixture
def mock_response(monkeypatch):"""Requests.get() mock返回json值: {'mock_key':'mock_response'}."""def mock_get(*args, **kwargs):return MockResponse()monkeypatch.setattr(requests, "get", mock_get)# 注意我们的测试函数传入的是fixture方法,而不是直接传入monkeypatch
def test_get_json(mock_response):result = app.get_json("https://fakeurl")assert result["mock_key"] == "mock_response"
注意:
建议使用 MonkeyPatch.context()修补程序来限制要测试的块, 该对象with在退出时撤消在块内完成的所有修补操作。
import functoolsdef test_partial(monkeypatch):with monkeypatch.context() as m:m.setattr(functools, "partial", 3)assert functools.partial == 3
猴子补丁字典例子
DEFAULT_CONFIG = {"user": "user1", "database": "db1"}def create_connection_string(config=None):config = config or DEFAULT_CONFIGreturn f"User Id={config['user']}; Location={config['database']};"def test_connection(monkeypatch):monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user")monkeypatch.setitem(app.DEFAULT_CONFIG, "database", "test_db")expected = "User Id=test_user; Location=test_db;"result = app.create_connection_string()assert result == expected
使用monkeypatch.delitem()删除值。
import pytestdef test_missing_user(monkeypatch):monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)'''因为未传递配置引发KeyError,并且默认值现在缺少“user”项。'''with pytest.raises(KeyError):_ = app.create_connection_string()
这篇关于pytest之Monkeypatching(猴子补丁)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!