2018年1月25日星期四

python单元测试中使用mock

部分内容转自:https://blog.laisky.com/p/unittest-mock/

Mock
MagicMock是Mock的扩展,允许使用magic methods,参考:https://docs.python.org/3/library/unittest.mock.html#magic-methods
PropertyMock可以mock属性值。

return_value:直接指定mock的返回值
side_effect:可以重写函数
spec:
spec 的适用条件稍微复杂一点,当我们使用 mock 实例的时候,没法指定该实例有哪些方法和属性是可以调用的, mock 会自动的对调用的每一个方法或属性创建返回一个 mock 实例。
但是如果我们希望只对指定的属性或方法使用 mock,就需要 spec 来满足这一需求。
先来看看不使用 spec 的情况:
mock = MagicMock()

dir(mock)
# ['assert_any_call', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', reset_mock', 'return_value', 'side_effect']

# 然后我们调用三个不存在的属性
mock.q
mock.w
mock.e

# 再来看看
# 注意 mock 自动创建增加了 q, w, e 方法
dir(mock)
# ['assert_any_call', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'e', 'method_calls', 'mock_add_spec', 'mock_calls', 'q', 'reset_mock', 'return_value', 'side_effect', 'w']
我们可以使用 spec 来显示的指定那些方法和属性是可 mock 的。
spec 是一个自定义类,在类里定义可以 mock 的方法和属性:
class SpecClass:

    attr1 = None
    attr2 = None


mock = MagicMock(spec=SpecClass)

print(mock.attr1)
# <MagicMock name='mock.attr1' id='4589618064'>

# 尝试调用未指定的属性时就会抛出 AttributeError
mock.attr3
# Traceback (most recent call last):
#           File "", line 1, in
#           File "/Users/laisky/.pyenv/versions/3.4.1/lib/python3.4/unittest/mock.py", line 568, in __getattr__
#             raise AttributeError("Mock object has no attribute %r" % name)
#         AttributeError: Mock object has no attribute 'attr3'
顺带一提 spec_set,和 spec 不同的一点在于 spec_set 传入的是一个实例而不是对象:
mock = MagicMock(spec=SpecClass)

# 等效于
mock = MagicMock(spec_set=SpecClass())
如果你只是简单希望只能调用显式 mock 过的方法和属性,又懒得去重复写一遍 spec,可以直接指定 spec=True。

Patch
patch 用来将指定(target)的实例替换为 mock 实例,主要用法有三种:
作为 decorator 修饰函数或类
作为 context 上下文管理器
作为 patch object,通过 start 和 stop 来管理 mock

1. @patch 装饰器
2. with patch() 作为上下文
3. patch.start()、patch.stop() 手动开启和关闭

没有评论:

发表评论