5、pytest -- 猴子补丁
有时候,测试用例需要调用某些依赖于全局配置的功能,或者这些功能本身又调用了某些不容易测试的代码(例如:网络接入)。fixture monkeypatch
可以帮助你安全的设置/删除一个属性、字典项或者环境变量,甚至改变导入模块时的sys.path
路径。
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)
所有的修改将在测试用例或者fixture
执行完成后撤销。raising
参数表明:当设置/删除操作的目标不存在时,是否上报KeyError
和AttributeError
异常。
1. 修改函数功能或者类属性
使用monkeypatch.setattr()
可以将函数或者属性修改为你希望的行为,使用monkeypatch.delattr()
可以删除测试用例使用的函数或者属性;
参考以下三个例子:
在这个例子中,使用
monkeypatch.setattr()
修改Path.home
方法,在测试运行期间,它一直返回的是固定的Path("/abc")
,这样就移除了它在不同平台上的依赖;测试运行完成后,对Path.home
的修改会被撤销;# src/chapter-5/test_module.py from pathlib import Path def getssh():
return Path.home() / ".ssh" def test_getssh(monkeypatch):
def mockreturn():
return Path("/abc") # 替换 Path.home
# 需要在真正的调用之前执行
monkeypatch.setattr(Path, "home", mockreturn) # 将会使用 mockreturn 代替 Path.home
x = getssh()
assert x == Path("/abc/.ssh")
在这个例子中,使用
monkeypatch.setattr()
结合类,模拟函数的返回对象;假设我们有一个简单的功能,访问一个
url
返回网页内容:# src/chapter-5/app.py from urllib import request def get(url):
r = request.urlopen(url)
return r.read().decode('utf-8')
我们现在要去模拟
r
,它需要一个.read()
方法返回的是bytes
的数据类型;我们可以在测试模块中定义一个类来代替r
:# src/chapter-5/test_app.py from urllib import request from app import get # 自定义的类模拟 urlopen 的返回值
class MockResponse: # 永远返回一个固定的 bytes 类型的数据
@staticmethod
def read():
return b'luizyao.com' def test_get(monkeypatch):
def mock_urlopen(*args, **kwargs):
return MockResponse() # 使用 request.mock_urlopen 代替 request.urlopen
monkeypatch.setattr(request, 'urlopen', mock_urlopen) data = get('https://luizyao.com')
assert data == 'luizyao.com'
你可以继续为实际的场景构建更具有复杂度的
MockResponse
;例如,你可以包含一个总是返回True
的ok
属性,或者根据输入的字符串为read()
返回不同的值;我们也可以通过
fixture
跨用例共享:# src/chapter-5/test_app.py import pytest # monkeypatch 是 function 级别作用域的,所以 mock_response 也只能是 function 级别,
# 否则会报 ScopeMismatch
@pytest.fixture
def mock_response(monkeypatch):
def mock_urlopen(*args, **kwargs):
return MockResponse() # 使用 request.mock_urlopen 代替 request.urlopen
monkeypatch.setattr(request, 'urlopen', mock_urlopen) # 使用 mock_response 代替原先的 monkeypatch
def test_get_fixture1(mock_response):
data = get('https://luizyao.com')
assert data == 'luizyao.com' # 使用 mock_response 代替原先的 monkeypatch
def test_get_fixture2(mock_response):
data = get('https://bing.com')
assert data == 'luizyao.com'
注意:
- 测试用例使用的
fixture
由原先的mock_response
替换为monkeypatch
; - 因为
monkeypatch
是function
级别作用域的,所以mock_response
也只能是function
级别,否则会报ScopeMismatch: You tried to access the 'function' scoped fixture 'monkeypatch' with a 'module' scoped request object
错误; - 如果你想让
mock_response
应用于所有的测试用例,可以考虑将它移到conftest.py
里面,并标记autouse=True
;
- 测试用例使用的
在这个例子中,使用
monkeypatch.delattr()
删除urllib.request.urlopen()
方法;# src/chapter-5/test_app.py @pytest.fixture
def no_request(monkeypatch):
monkeypatch.delattr('urllib.request.urlopen') def test_delattr(no_request):
data = get('https://bing.com')
assert data == 'luizyao.com'
执行:
λ pipenv run pytest --tb=native --assert=plain --capture=no src/chapter-5/test_app.
py::test_delattr
=============================== test session starts ================================
platform win32 -- Python 3.7.3, pytest-5.1.3, py-1.8.0, pluggy-0.13.0
rootdir: D:\Personal Files\Projects\pytest-chinese-doc
collected 1 item src\chapter-5\test_app.py F ===================================== FAILURES =====================================
___________________________________ test_delattr ___________________________________
Traceback (most recent call last):
File "D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-5\test_app.py", line 78, in test_delattr
data = get('https://bing.com')
File "D:\Personal Files\Projects\pytest-chinese-doc\src\chapter-5\app.py", line 26, in get
r = request.urlopen(url)
AttributeError: module 'urllib.request' has no attribute 'urlopen'
================================ 1 failed in 0.04s =================================
注意:
避免删除内置库中的方法,如果一定要这么做,最好加上
--tb=native --assert=plain --capture=no
;修改
pytest
使用到的库,可能会污染pytest
本身,建议使用MonkeyPatch.context()
,它返回一个MonkeyPatch
对象,结合with
限制这些修改只发生在包裹的代码中。def test_stdlib(monkeypatch):
with monkeypatch.context() as m:
m.setattr(functools, "partial", 3)
assert functools.partial == 3
2. 修改环境变量
使用monkeypatch
的setenv()
和delenv()
方法,可以在测试中安全的设置/删除环境变量;
# src/chapter-5/test_env.py
import os
import pytest
def get_os_user():
username = os.getenv('USER')
if username is None:
raise IOError('"USER" environment variable is not set.')
return username
def test_user(monkeypatch):
monkeypatch.setenv('USER', 'luizyao')
assert get_os_user() == 'luizyao'
def test_raise_exception(monkeypatch):
monkeypatch.delenv('USER', raising=False)
pytest.raises(IOError, get_os_user)
monkeypatch.delenv()
的raising
要设置为False
,否则可能会报KeyError
;
你也可以使用fixture
,实现跨用例共享:
import pytest
@pytest.fixture
def mock_env_user(monkeypatch):
monkeypatch.setenv("USER", "TestingUser")
@pytest.fixture
def mock_env_missing(monkeypatch):
monkeypatch.delenv("USER", raising=False)
# notice the tests reference the fixtures for mocks
def test_upper_to_lower(mock_env_user):
assert get_os_user_lower() == "testinguser"
def test_raise_exception(mock_env_missing):
with pytest.raises(OSError):
_ = get_os_user_lower()
3. 修改字典
使用monkeypatch.setitem()
方法可以在测试期间安全的修改字典中特定的值;
DEFAULT_CONFIG = {"user": "user1", "database": "db1"}
def create_connection_string(config=None):
config = config or DEFAULT_CONFIG
return f"User Id={config['user']}; Location={config['database']};"
我们可以修改数据库的用户或者使用其它的数据库:
import app
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 pytest
import app
def test_missing_user(monkeypatch):
monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)
with pytest.raises(KeyError):
_ = app.create_connection_string()
5、pytest -- 猴子补丁的更多相关文章
- [Effective JavaScript 笔记]第42条:避免使用轻率的猴子补丁
41条对违反抽象原则行为的讨论之后,下面聊一聊终极违例.由于对象共享原型,因此每一个对象都可以增加.删除或修改原型的属性.这个有争议的实践通常称为猴子补丁. 猴子补丁示例 猴子补丁的吸引力在于其强大. ...
- Python Monkey patch猴子补丁
monkey patch (猴子补丁) 用来在运行时动态修改已有的代码,而不需要修改原始代码. 简单的monkey patch 实现:[python] #coding=utf-8 def orig ...
- python中的猴子补丁Monkey Patch
python中的猴子补丁Monkey Patch 什么是猴子补丁 the term monkey patch only refers to dynamic modifications of a cla ...
- python设计模式之猴子补丁模式
1.所有书中都没有把猴子补丁作为一种设计模式来看待.因为设计模式的模式的命名是根据java中提炼出来的,语言方式决定了java绝对不会有也不需要有这种操作,不存在的.那自然设计模式不会包括猴子补丁模式 ...
- Python之猴子补丁
1.在运行时,对属性,方法,函数等进行动态替换 2.其目的往往是为了通过替换,修改来增强,扩展原有代码的能力 #test2.py class Person: def get_score(self): ...
- python 模块会导入几次?猴子补丁为什么可以实现?
一共三个文件 a.py内容是 print('被导入') x = 1 b.py内容是 import a a.x = 2 c.py内容是 import a import b print(a.x) 现在运行 ...
- 第二种方式,修改python unittest的执行顺序,使用猴子补丁
1.按照测试用例的上下顺序,而不是按方法的名称的字母顺序来执行测试用例. 之前的文章链接 python修改python unittest的运行顺序 之前写的,不是猴子补丁,而是要把Test用例的类名传 ...
- python monkey 猴子补丁技术编程,修改python json dumps方法。
1.猴子补丁就是不改变原有模块的内容的前提下,给原有模块新增方法或者修改原有模块. 一个模块的函数,如果希望改变函数的功能,不改变函数名,通常是库模块,你不可能去修改三方库的源码的,实施起来不方便,而 ...
- Python猴子补丁
属性在运行时的动态替换,叫做猴子补丁(Monkey Patch). 为什么叫猴子补丁 属性的运行时替换和猴子也没什么关系,关于猴子补丁的由来网上查到两种说法: 1,这个词原来为Guerrilla Pa ...
随机推荐
- java字符串,数组,集合框架重点
1.字符串的字面量是否自动生成一个字符串的变量? String str1 = “abc”; Sring str2 = new String (“abc”); 对于str1:Jvm在遇到双 ...
- JS 取整、取余
一.取整 1. 取整 // 丢弃小数部分,保留整数部分 parseInt(7/2) // 3 2. 向上取整 // 向上取整,有小数就整数部分加1 Math.ceil(7/2) // 4 3. 向下取 ...
- .net core 3.0 Signalr - 02 使用强类型的Hub
## 强类型的优缺点 - 优点 强类型的Hub可以避免魔法函数名,相比弱类型更容易维护和发现问题,直接上代码 - 缺点 特么的得多些好几行代码 ## 代码 ### 接口定义 ``` C# /// // ...
- openstack问题记录
先去查看对应的日志:/var/log/,再来排查错误 1.实例处于错误状态 解决办法: 1.使用openstack hypervisor list查看 2.然后openstack hypervisor ...
- dede tag标签静态化
看回那2个文件夹即可,txt说明书我已经修改过. 下面说一下tag标签静态化之后在内容页.列表页中如何使用. 内容页中沿用之前的方法即可: {dede:tag sort='new' getall='0 ...
- 谷歌助力,快速实现 Java 应用容器化
原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. Google 在 2018 年下旬开源 ...
- Nebula 架构剖析系列(一)图数据库的存储设计
摘要 在讨论某个数据库时,存储 ( Storage ) 和计算 ( Query Engine ) 通常是讨论的热点,也是爱好者们了解某个数据库不可或缺的部分.每个数据库都有其独有的存储.计算方式,今天 ...
- 02-18 scikit-learn库之k近邻算法
目录 scikit-learn库之k近邻算法 一.KNeighborsClassifier 1.1 使用场景 1.2 代码 1.3 参数详解 1.4 方法 1.4.1 kneighbors([X, n ...
- Windows常用操作
目录 查询IP地址 常用快捷键 显示文件后缀名 查询IP地址 1.进入到dos界面 2.输入命令: ipconfig 常用快捷键 快捷键 作用 win+E 打开计算机 win+R 打开运行 win+R ...
- Ubuntu使用中遇到的的一些问题
制作ubuntu启动盘后,U盘只读. ubuntu自带的"启动盘创建器(usb-creator-gtk)"制作启动盘后,U盘只读. 打开ubuntu自带的"磁盘(hard ...