mock

MagicMock 经常接触到的类

参见

应用案例: Lib/test/test/test_asynciotest_base_events.py line:30。 在这个例子中,是模块级的mock,使用MagicMock mock整个socket模块,然后继续使用MagicMock mock 模块里的socket函数。

patch 估计是用得最多的函数

  • target target传入一个字符串,格式为’package.module.ClassName’, 基本原则是你在查找对象的地方做补丁,这个地方不一定和定义对象的地方相同。

小技巧

如果不确定target的值,可以把要mock的对象打印出来,打印的内容可以帮你快速确定target的值。

警告

双下划线开头的方法不能mock

  • new 如果传None,则mock对象不会作为参数传入到测试函数

def func():
    pass

class T(TestCase):
    @mock.patch('__main__.func', new=None)
    def test_case1(self):
        pass

    @mock.patch('__main__.func')
    def test_case2(self, mock_b):
        pass
  • spec 创建指定的属性和方法

传入一个列表时:

m = mock.Mock(spec=['attr1', 'attr2'])
print(m.attr1)
print(m.attr2)

传入True时,创建被Mock对象一样的方法, 属性需要自行手工创建。

传入一个class对象或者实例对象, 创建class对象或者实例对象一样的属性和方法

  • spec_set spec更严格的变体,功能一致。指定spec参数,可以为mock对象创建不存在的属性赋值。指定spec_set参数,为mock对象不存在的属性赋值则抛出异常。

  • new_callable 替换默认的MagicMock对象。也就是可以自定义个Mock对象。

多次调用同一函数,验证每次传入的参数

from unittest.mock import Mock, call
# 【setup】 -------------------
mock = Mock(return_value=None)
mock(1)
mock(2)
mock(3)
mock(4)
calls = [call(2), call(3)]
# 【assert】 --------------------
mock.assert_has_calls(calls)
calls = [call(4), call(2), call(3)]
# 不限制调用顺序
mock.assert_has_calls(calls, any_order=True)

跟踪方法调用了几次,传入了哪些参数

class SMTP:

    def send(self):
        print(self)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        ...

with patch('__main__.SMTP') as smtp:
    with SMTP() as s:
        s.send()

    # 追踪s的调用情况, s等价于smtp.return_value, 不要使用smtp(),这样是重新实例化一次了
    print(smtp.return_value.__enter__.return_value.call_count)

小技巧

对于返回实例的,使用return_value替代()来追踪实例的调用过程, 使用()是调用一次方法了


常见问题

如何mock内置函数

@mock.patch('builtins.open')

如果在自定义的package.module中使用set,想mock set类:

@mock.patch('package.module.set')

参考自 https://docs.python.org/zh-cn/3/library/unittest.mock.html#patch-builtins

Mock和MagicMock的区别

MagicMock预实现了魔术方法(__iter__, __enter__这种), 其他没有区别

patch和patch.object的区别

patch的target参数传入一个字符串,patch.object的target传入一个实例化对象,其他没有区别

如何mock super()的调用

假设B类继承自A类,mock B调用A.func方法, super(B, self).func()

@mock.path(A.func)

开发技巧

简化patch的target写入

灵感来自test_asyncio.test_base_events.py patch_socket函数

使用with来patch

如果一个test_方法里面mock对象有不同的测试场景,可以多次使用with的方式比修饰器更好些。

样例: test_asyncio.test_base_events.py 的test_default_exc_handler_broken方法。

使用Mock作为self调用真实的方法

只能逐个方法设置:

class A:
    def __init__(self):
        pass

    def print(self):
        print(self)

# 深拷贝A类
B = type('B', (A.__base__,), dict(A.__dict__))

with mock.patch('__main__.A') as o:
    a = A()
    print(a.print)
    # Mock调用
    a.print()

    def _print():
        _self = a
        B.print(_self)

    o.print.side_effect = _print

    # 真实调用,不过self为Mock类
    a.print()

在pytest使用mock

使用 pytest-mock 插件