# patch

# patch函数签名

patch()​ 接受一个已存在对象的全路径名,将其替换为一个新的值。 原来的值会在装饰器函数或上下文管理器完成后自动恢复回来。 默认情况下,所有值会被 MagicMock​ 实例替代。被默认用来作为替换值的 <span class="pre">MagicMock</span>​ 实例能够模拟可调用对象和实例。 他们记录对象的使用信息并允许执行断言检查。

patch(target, new=sentinel.DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)
    `patch` acts as a function decorator, class decorator or a context
    manager. Inside the body of the function or with statement, the `target`
    is patched with a `new` object. When the function/with statement exits
    the patch is undone.

    If `new` is omitted, then the target is replaced with a
    `MagicMock`. If `patch` is used as a decorator and `new` is
    omitted, the created mock is passed in as an extra argument to the
    decorated function. If `patch` is used as a context manager the created
    mock is returned by the context manager.

    `target` should be a string in the form `'package.module.ClassName'`. The
    `target` is imported and the specified object replaced with the `new`
    object, so the `target` must be importable from the environment you are
    calling `patch` from. The target is imported when the decorated function
    is executed, not at decoration time.

    The `spec` and `spec_set` keyword arguments are passed to the `MagicMock`
    if patch is creating one for you.

    In addition you can pass `spec=True` or `spec_set=True`, which causes
    patch to pass in the object being mocked as the spec/spec_set object.

    `new_callable` allows you to specify a different class, or callable object,
    that will be called to create the `new` object. By default `MagicMock` is
    used.

    A more powerful form of `spec` is `autospec`. If you set `autospec=True`
    then the mock will be created with a spec from the object being replaced.
    All attributes of the mock will also have the spec of the corresponding
    attribute of the object being replaced. Methods and functions being
    mocked will have their arguments checked and will raise a `TypeError` if
    they are called with the wrong signature. For mocks replacing a class,
    their return value (the 'instance') will have the same spec as the class.

    Instead of `autospec=True` you can pass `autospec=some_object` to use an
    arbitrary object as the spec instead of the one being replaced.

    By default `patch` will fail to replace attributes that don't exist. If
    you pass in `create=True`, and the attribute doesn't exist, patch will
    create the attribute for you when the patched function is called, and
    delete it again afterwards. This is useful for writing tests against
    attributes that your production code creates at runtime. It is off by
    default because it can be dangerous. With it switched on you can write
    passing tests against APIs that don't actually exist!

    Patch can be used as a `TestCase` class decorator. It works by
    decorating each test method in the class. This reduces the boilerplate
    code when your test methods share a common patchings set. `patch` finds
    tests by looking for method names that start with `patch.TEST_PREFIX`.
    By default this is `test`, which matches the way `unittest` finds tests.
    You can specify an alternative prefix by setting `patch.TEST_PREFIX`.

    Patch can be used as a context manager, with the with statement. Here the
    patching applies to the indented block after the with statement. If you
    use "as" then the patched object will be bound to the name after the
    "as"; very useful if `patch` is creating a mock object for you.

    `patch` takes arbitrary keyword arguments. These will be passed to
    the `Mock` (or `new_callable`) on construction.

    `patch.dict(...)`, `patch.multiple(...)` and `patch.object(...)` are
    available for alternate use-cases.
  • target​​参数必须是一个str,格式为'package.module.ClassName​​'
  • new​​默认指定为MagicMock​​
  • spec=True​​或spec_set=True​​,这会导致patch​​传递给被模拟为spec / spec_set​​的对象
  • new_callable​​允许您指定将被调用以创建新对象的不同类或可调用对象。默认情况下MagicMock​​使用。
  • return_value​可以直接指定被patch对象返回的值-以此就可以不指定new_callable

极简示例:

# mymodule.py

def urlprint(protocol, host, domain):
    url = '{}://{}.{}'.format(protocol, host, domain)
    print(url)

# test_url_print.py
from io import StringIO
from unittest import TestCase
from unittest.mock import patch
import mymodule

class TestURLPrint(TestCase):
    def test_url_gets_to_stdout(self):
        protocol = 'http'
        host = 'www'
        domain = 'example.com'
        expected_url = '{}://{}.{}\n'.format(protocol, host, domain)

        with patch('sys.stdout', new=StringIO()) as fake_out:
            mymodule.urlprint(protocol, host, domain)
            self.assertEqual(fake_out.getvalue(), expected_url)
from unittest import TestCase, mock
from my_module import my_function

class TestMyFunction(TestCase):
  
    def test_my_function(self):
        with mock.patch('my_module.my_dependency', return_value=42):
            result = my_function()
        self.assertEqual(result, 43)

# 手动调用patch

p = patch('example.func')
mock_func = p.start()
example.func(x)
mock_func.assert_called_with(x)
p.stop()

与之等价的是:

with patch('example.func') as mock_func:
    example.func(x)      # Uses patched example.func
    mock_func.assert_called_with(x)

# 和

from unittest.mock import patch
import test

@patch('test.fun')
def test1(x, mock_func):
    test.fun(x)       # Uses patched example.func
    print(mock_func is test.fun)        # True
    mock_func.assert_called_with(x)

test1("1")

# patch装饰器

# 函数patch示例

  1. 新建一个temple.py,写入以下代码

    # temple.py
    
    
    def zhifu():
        '''假设这里是一个支付的功能,未开发完
        支付成功返回:{"result": "success", "reason":"null"}
        支付失败返回:{"result": "fail", "reason":"余额不足"}
        reason返回失败原因
        '''
        pass
    
    def zhifu_statues():
        '''根据支付的结果success or fail,判断跳转到对应页面'''
        result = zhifu()
        print(result)
        try:
            if result["result"] == "success":
                return "支付成功"
            elif result["result"] == "fail":
                print("失败原因:%s" % result["reason"])
                return "支付失败"
            else:
                return "未知错误异常"
        except:
            return "Error, 服务端返回异常!"
    
  2. 用mock.patch实现如下:

    from unittest import mock
    import unittest
    import temple
    
    class Test_zhifu_statues(unittest.TestCase):
        '''单元测试用例'''
    
        @mock.patch("temple.zhifu")
        def test_01(self, mock_zhifu):
            '''测试支付成功场景'''
            # 方法一:mock一个支付成功的数据
            # temple.zhifu = mock.Mock(return_value={"result": "success", "reason":"null"})
    
            # 方法二:mock.path装饰器模拟返回结果
            mock_zhifu.return_value = {"result": "success", "reason":"null"}
            # 根据支付结果测试页面跳转
            statues = temple.zhifu_statues()
            print(statues)
            self.assertEqual(statues, "支付成功")
    
        @mock.patch("temple.zhifu")
        def test_02(self, mock_zhifu):
            '''测试支付失败场景'''
            # mock一个支付成功的数据
            mock_zhifu.return_value = {"result": "fail", "reason": "余额不足"}
            # 根据支付结果测试页面跳转
            statues = temple.zhifu_statues()
            self.assertEqual(statues, "支付失败")
    
    if __name__ == "__main__":
        unittest.main()
    

# 类和方法ptach示例

  1. 创建类

    # 保存为temple.py
    class Zhifu():
        def zhifu(self):
            '''假设这里是一个支付的功能,未开发完
            支付成功返回:{"result": "success", "reason":"null"}
            支付失败返回:{"result": "fail", "reason":"余额不足"}
            reason返回失败原因
            '''
            pass
    
    class Statues():
        def zhifu_statues(self):
            '''根据支付的结果success or fail,判断跳转到对应页面'''
            result = Zhifu().zhifu()
            print(result)
            try:
                if result["result"] == "success":
                    return "支付成功"
                elif result["result"] == "fail":
                    print("失败原因:%s" % result["reason"])
                    return "支付失败"
                else:
                    return "未知错误异常"
            except:
                return "Error, 服务端返回异常!"
    
  2. 用例设计如下

    from unittest import mock
    import unittest
    from temple_class import Zhifu,Statues
    
    class Test_zhifu_statues(unittest.TestCase):
        '''单元测试用例'''
    
        @mock.patch("temple_class.Zhifu")
        def test_01(self, mock_Zhifu):
            '''测试支付成功场景'''
            a = mock_Zhifu.return_value  # 先返回实例,对类名称替换
            # 通过实例调用方法,再对方法的返回值替换
            a.zhifu.return_value = {"result": "success", "reason":"null"}
            # 根据支付结果测试页面跳转
            statues = Statues().zhifu_statues()
            print(statues)
            self.assertEqual(statues, "支付成功")
    
        @mock.patch("temple_class.Zhifu")
        def test_02(self, mock_Zhifu):
            '''测试支付失败场景'''
            b = mock_Zhifu.return_value  # 先返回实例,对类名称替换
            # 通过实例调用方法,再对方法的返回值替换
            b.zhifu.return_value = {"result": "fail", "reason": "余额不足"}
            # 根据支付结果测试页面跳转
            statues = Statues().zhifu_statues()
            print(statues)
            self.assertEqual(statues, "支付失败")
    
    if __name__ == "__main__":
        unittest.main()
    
  3. 相当于函数来说主要多一步-要先对类的名称进行mock一次"a = mock_Zhifu.return_value​",再通过实例去调用方法

# patch上下文管理器

patch支持作为上下文管理器进行使用,例如有如下函数以及测试:

# mymodule.py

def urlprint(protocol, host, domain):
    url = '{}://{}.{}'.format(protocol, host, domain)
    print(url)
from io import StringIO
from unittest import TestCase
from unittest.mock import patch
import mymodule

class TestURLPrint(TestCase):
    def test_url_gets_to_stdout(self):
        protocol = 'http'
        host = 'www'
        domain = 'example.com'
        expected_url = '{}://{}.{}\n'.format(protocol, host, domain)

        with patch('sys.stdout', new=StringIO()) as fake_out:
            mymodule.urlprint(protocol, host, domain)
            self.assertEqual(fake_out.getvalue(), expected_url)