有时候,测试用例需要调用某些依赖于全局配置的功能,或者这些功能本身又调用了某些不容易测试的代码(例如:网络接入)。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参数表明:当设置/删除操作的目标不存在时,是否上报KeyErrorAttributeError异常。

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;例如,你可以包含一个总是返回Trueok属性,或者根据输入的字符串为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
    • 因为monkeypatchfunction级别作用域的,所以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. 修改环境变量

使用monkeypatchsetenv()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()

GitHub仓库地址:https://github.com/luizyao/pytest-chinese-doc

5、pytest -- 猴子补丁的更多相关文章

  1. [Effective JavaScript 笔记]第42条:避免使用轻率的猴子补丁

    41条对违反抽象原则行为的讨论之后,下面聊一聊终极违例.由于对象共享原型,因此每一个对象都可以增加.删除或修改原型的属性.这个有争议的实践通常称为猴子补丁. 猴子补丁示例 猴子补丁的吸引力在于其强大. ...

  2. Python Monkey patch猴子补丁

    monkey patch (猴子补丁)   用来在运行时动态修改已有的代码,而不需要修改原始代码. 简单的monkey patch 实现:[python] #coding=utf-8 def orig ...

  3. python中的猴子补丁Monkey Patch

    python中的猴子补丁Monkey Patch 什么是猴子补丁 the term monkey patch only refers to dynamic modifications of a cla ...

  4. python设计模式之猴子补丁模式

    1.所有书中都没有把猴子补丁作为一种设计模式来看待.因为设计模式的模式的命名是根据java中提炼出来的,语言方式决定了java绝对不会有也不需要有这种操作,不存在的.那自然设计模式不会包括猴子补丁模式 ...

  5. Python之猴子补丁

    1.在运行时,对属性,方法,函数等进行动态替换 2.其目的往往是为了通过替换,修改来增强,扩展原有代码的能力 #test2.py class Person: def get_score(self): ...

  6. python 模块会导入几次?猴子补丁为什么可以实现?

    一共三个文件 a.py内容是 print('被导入') x = 1 b.py内容是 import a a.x = 2 c.py内容是 import a import b print(a.x) 现在运行 ...

  7. 第二种方式,修改python unittest的执行顺序,使用猴子补丁

    1.按照测试用例的上下顺序,而不是按方法的名称的字母顺序来执行测试用例. 之前的文章链接 python修改python unittest的运行顺序 之前写的,不是猴子补丁,而是要把Test用例的类名传 ...

  8. python monkey 猴子补丁技术编程,修改python json dumps方法。

    1.猴子补丁就是不改变原有模块的内容的前提下,给原有模块新增方法或者修改原有模块. 一个模块的函数,如果希望改变函数的功能,不改变函数名,通常是库模块,你不可能去修改三方库的源码的,实施起来不方便,而 ...

  9. Python猴子补丁

    属性在运行时的动态替换,叫做猴子补丁(Monkey Patch). 为什么叫猴子补丁 属性的运行时替换和猴子也没什么关系,关于猴子补丁的由来网上查到两种说法: 1,这个词原来为Guerrilla Pa ...

随机推荐

  1. Spring IOC(1)----容器刷新(refresh())之前

    首先本次分析是基于注解形式的,想来xml格式的原理都是类似的. 首先说一下什么是Bean定义(beandefinition):bean定义并不是实例化的bean,而是bean对象的一些信息,根据这些定 ...

  2. [docker swarm] 从单容器走向负载均衡部署

    背景 之前写过<<docker-compose真香>> 和<docker-compose.docker stack前世今生>两篇博客, 回顾一下思路: ① dock ...

  3. texlive支持中文的简单方法

    1.确保tex文件的编码方式是UTF-8, 2.在文档开始处添加一行命令即可,即 \usepackage[UTF8]{ctex} , 如下所示: \documentclass{article} \us ...

  4. 【CV现状-3.3】特征提取与描述

    #磨染的初心--计算机视觉的现状 [这一系列文章是关于计算机视觉的反思,希望能引起一些人的共鸣.可以随意传播,随意喷.所涉及的内容过多,将按如下内容划分章节.已经完成的会逐渐加上链接.] 缘起 三维感 ...

  5. 虚拟机安装Centos7系统后优化操作

    重点说明 以下操作针对于VMware软件上新创建的Centos7的虚拟机的优化,当需要多台虚拟机的实验环境时,可通过以下需求先操作配置出一台优化机(也可称为模板机),并创建快照记录,以后的多台虚拟机环 ...

  6. bullet物理引擎与OpenGL结合 导入3D模型进行碰撞检测 以及画三角网格的坑

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11681069.html 一.初始化世界以及模型 /// 冲突配置包含内存的默认设置,冲突设置. ...

  7. react native ios 上架

    1.申请开发者账号,去苹果开发者中心申请 2.applicationloader 集申请证书.真机调试.发布于一身,避免繁琐的官网申请过程 http://www.applicationloader.n ...

  8. sublime text2解决中文乱码,支持中文的设置方法

    步骤: 1.安装Sublime Package Control.        在Sublime Text 2上用Ctrl+-打开控制台并在里面输入以下代码,Sublime Text 2就会自动安装P ...

  9. LeetCode 第 287 号问题:寻找重复数,一道非常简单的数组遍历题,加上四个条件后感觉无从下手

    今天分享的题目来源于 LeetCode 第 287 号问题:寻找重复数. 题目描述 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个 ...

  10. Python接口测试框架实战与自动化进阶☝☝☝

    Python接口测试框架实战与自动化进阶☝☝☝  一.fiddler在工作中的运用  1.如何抓接口 抓紧手机端接口 ①.在电脑终端输入:ipconfig ,找到电脑ip ②.打开手机,连接WiFi, ...