唯快不破:mock源码剖析
有时,你需要为单元测试的初始设置准备一些“其他”的代码资源。但这些资源兴许会不可用,不稳定,或者是使用起来太笨重。你可以试着找一些其他的资源替代;或者你可以通过创建一个被称为mock的东西来模拟它。Mocks能够让我们模拟那些在单元测试中不可用或太笨重的资源。总之就是mock可以屏蔽一些我们不需要关系的细节。
mock.py
# mock.py
# Test tools for mocking and patching.
# E-mail: fuzzyman AT voidspace DOT org DOT uk
#
# mock 1.0.1
# http://www.voidspace.org.uk/python/mock/
#
# Copyright (c) 2007-2013, Michael Foord & the mock team
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.from __future__ import absolute_import__all__ = ('__version__','version_info','Mock','MagicMock','patch','sentinel','DEFAULT','ANY','call','create_autospec','FILTER_DIR','CallableMixin','NonCallableMock','NonCallableMagicMock','mock_open','PropertyMock',
)from functools import partial
import inspect
import pprint
import sys
try:import builtins
except ImportError:import __builtin__ as builtins
from types import ModuleTypeimport six
from six import wraps
from pbr.version import VersionInfo_v = VersionInfo('mock').semantic_version()
__version__ = _v.release_string()
version_info = _v.version_tuple()import mocktry:inspectsignature = inspect.signature
except AttributeError:import funcsigsinspectsignature = funcsigs.signature# TODO: use six.
try:unicode
except NameError:# Python 3basestring = unicode = strtry:long
except NameError:# Python 3long = inttry:BaseException
except NameError:# Python 2.4 compatibilityBaseException = Exceptionif six.PY2:# Python 2's next() can't handle a non-iterator with a __next__ method._next = nextdef next(obj, _next=_next):if getattr(obj, '__next__', None):return obj.__next__()return _next(obj)del _next_builtins = set(name for name in dir(builtins) if not name.startswith('_'))BaseExceptions = (BaseException,)
if 'java' in sys.platform:# jythonimport javaBaseExceptions = (BaseException, java.lang.Throwable)try:_isidentifier = str.isidentifier
except AttributeError:# Python 2.Ximport keywordimport reregex = re.compile(r'^[a-z_][a-z0-9_]*$', re.I)def _isidentifier(string):if string in keyword.kwlist:return Falsereturn regex.match(string)self = 'im_self'
builtin = '__builtin__'
if six.PY3:self = '__self__'builtin = 'builtins'# NOTE: This FILTER_DIR is not used. The binding in mock.FILTER_DIR is.
FILTER_DIR = True# Workaround for Python issue #12370
# Without this, the __class__ properties wouldn't be set correctly
_safe_super = superdef _is_instance_mock(obj):# can't use isinstance on Mock objects because they override __class__# The base class for all mocks is NonCallableMockreturn issubclass(type(obj), NonCallableMock)def _is_exception(obj):return (isinstance(obj, BaseExceptions) orisinstance(obj, ClassTypes) and issubclass(obj, BaseExceptions))class _slotted(object):__slots__ = ['a']DescriptorTypes = (type(_slotted.a),property,
)def _get_signature_object(func, as_instance, eat_self):"""获取任何可调用对象的签名对象.Given an arbitrary, possibly callable object, try to create a suitablesignature object.Return a (reduced func, signature) tuple, or None."""if isinstance(func, ClassTypes) and not as_instance:# 如果是类型,就返回__init__方法签名# If it's a type and should be modelled as a type, use __init__.try:func = func.__init__except AttributeError:return None# Skip the `self` argument in __init__# 跳过self参数eat_self = Trueelif not isinstance(func, FunctionTypes):# 不是方法的其他对象都返回__call__方法签名# If we really want to model an instance of the passed type,# __call__ should be looked up, not __init__.try:func = func.__call__except AttributeError:return Noneif eat_self:# 跳过selfsig_func = partial(func, None)else:sig_func = functry:return func, inspectsignature(sig_func)except ValueError:# Certain callable types are not supported by inspect.signature()return Nonedef _check_signature(func, mock, skipfirst, instance=False):# 为mock对象设置方法签名检查方法sig = _get_signature_object(func, instance, skipfirst)if sig is None:returnfunc, sig = sigdef checksig(_mock_self, *args, **kwargs):# 执行函数签名检查sig.bind(*args, **kwargs)# 拷贝方法其他元信息_copy_func_details(func, checksig)type(mock)._mock_check_sig = checksigdef _copy_func_details(func, funcopy):u"""拷贝方法元信息"""funcopy.__name__ = func.__name__funcopy.__doc__ = func.__doc__try:funcopy.__text_signature__ = func.__text_signature__except AttributeError:pass# we explicitly don't copy func.__dict__ into this copy as it would# expose original attributes that should be mockedtry:funcopy.__module__ = func.__module__except AttributeError:passtry:funcopy.__defaults__ = func.__defaults__except AttributeError:passtry:funcopy.__kwdefaults__ = func.__kwdefaults__except AttributeError:passif six.PY2:funcopy.func_defaults = func.func_defaultsreturndef _callable(obj):if isinstance(obj, ClassTypes):return Trueif getattr(obj, '__call__', None) is not None:return Truereturn Falsedef _is_list(obj):# checks for list or tuples# XXXX badly named!return type(obj) in (list, tuple)def _instance_callable(obj):"""Given an object, return True if the object is callable.For classes, return True if instances would be callable."""if not isinstance(obj, ClassTypes):# already an instancereturn getattr(obj, '__call__', None) is not Noneif six.PY3:# *could* be broken by a class overriding __mro__ or __dict__ via# a metaclassfor base in (obj,) + obj.__mro__:if base.__dict__.get('__call__') is not None:return Trueelse:klass = obj# uses __bases__ instead of __mro__ so that we work with old style classesif klass.__dict__.get('__call__') is not None:return Truefor base in klass.__bases__:if _instance_callable(base):return Truereturn Falsedef _set_signature(mock, original, instance=False):# 封装mock对象为带签名检查的函数对象# creates a function with signature (*args, **kwargs) that delegates to a# mock. It still does signature checking by calling a lambda with the same# signature as the original.if not _callable(original):returnskipfirst = isinstance(original, ClassTypes)result = _get_signature_object(original, instance, skipfirst)if result is None:returnfunc, sig = resultdef checksig(*args, **kwargs):sig.bind(*args, **kwargs)_copy_func_details(func, checksig)name = original.__name__if not _isidentifier(name):# 与关键字重名name = 'funcopy'context = {'_checksig_': checksig, 'mock': mock}src = """def %s(*args, **kwargs):_checksig_(*args, **kwargs)return mock(*args, **kwargs)""" % namesix.exec_(src, context)funcopy = context[name]_setup_func(funcopy, mock)return funcopydef _setup_func(funcopy, mock):funcopy.mock = mock# can't use isinstance with mocksif not _is_instance_mock(mock):returndef assert_called_with(*args, **kwargs):return mock.assert_called_with(*args, **kwargs)def assert_called_once_with(*args, **kwargs):return mock.assert_called_once_with(*args, **kwargs)def assert_has_calls(*args, **kwargs):return mock.assert_has_calls(*args, **kwargs)def assert_any_call(*args, **kwargs):return mock.assert_any_call(*args, **kwargs)def reset_mock():funcopy.method_calls = _CallList()funcopy.mock_calls = _CallList()mock.reset_mock()ret = funcopy.return_valueif _is_instance_mock(ret) and not ret is mock:ret.reset_mock()funcopy.called = Falsefuncopy.call_count = 0funcopy.call_args = Nonefuncopy.call_args_list = _CallList()funcopy.method_calls = _CallList()funcopy.mock_calls = _CallList()funcopy.return_value = mock.return_valuefuncopy.side_effect = mock.side_effectfuncopy._mock_children = mock._mock_childrenfuncopy.assert_called_with = assert_called_withfuncopy.assert_called_once_with = assert_called_once_withfuncopy.assert_has_calls = assert_has_callsfuncopy.assert_any_call = assert_any_callfuncopy.reset_mock = reset_mockmock._mock_delegate = funcopydef _is_magic(name):return '__%s__' % name[2:-2] == nameclass _SentinelObject(object):"""哨兵对象""""A unique, named, sentinel object."def __init__(self, name):self.name = namedef __repr__(self):return 'sentinel.%s' % self.nameclass _Sentinel(object):"""Access attributes to return a named object, usable as a sentinel."""def __init__(self):self._sentinels = {}def __getattr__(self, name):"""通过访问属性返回一个命名的哨兵对象"""if name == '__bases__':# Without this help(unittest.mock) raises an exceptionraise AttributeErrorreturn self._sentinels.setdefault(name, _SentinelObject(name))sentinel = _Sentinel()DEFAULT = sentinel.DEFAULT
_missing = sentinel.MISSING
_deleted = sentinel.DELETEDclass OldStyleClass:pass
ClassType = type(OldStyleClass)def _copy(value):if type(value) in (dict, list, tuple, set):return type(value)(value)return valueClassTypes = (type,)
if six.PY2:ClassTypes = (type, ClassType)_allowed_names = set(('return_value', '_mock_return_value', 'side_effect','_mock_side_effect', '_mock_parent', '_mock_new_parent','_mock_name', '_mock_new_name'
))def _delegating_property(name):_allowed_names.add(name)_the_name = '_mock_' + namedef _get(self, name=name, _the_name=_the_name):sig = self._mock_delegateif sig is None:return getattr(self, _the_name)return getattr(sig, name)def _set(self, value, name=name, _the_name=_the_name):sig = self._mock_delegateif sig is None:self.__dict__[_the_name] = valueelse:setattr(sig, name, value)return property(_get, _set)class _CallList(list):def __contains__(self, value):if not isinstance(value, list):return list.__contains__(self, value)len_value = len(value)len_self = len(self)if len_value > len_self:return Falsefor i in range(0, len_self - len_value + 1):sub_list = self[i:i+len_value]if sub_list == value:return Truereturn Falsedef __repr__(self):return pprint.pformat(list(self))def _check_and_set_parent(parent, value, name, new_name):if not _is_instance_mock(value):return Falseif ((value._mock_name or value._mock_new_name) or(value._mock_parent is not None) or(value._mock_new_parent is not None)):return False_parent = parentwhile _parent is not None:# setting a mock (value) as a child or return value of itself# should not modify the mockif _parent is value:return False_parent = _parent._mock_new_parentif new_name:value._mock_new_parent = parentvalue._mock_new_name = new_nameif name:value._mock_parent = parentvalue._mock_name = namereturn True# Internal class to identify if we wrapped an iterator object or not.
class _MockIter(object):def __init__(self, obj):self.obj = iter(obj)def __iter__(self):return selfdef __next__(self):return next(self.obj)class Base(object):_mock_return_value = DEFAULT_mock_side_effect = Nonedef __init__(self, *args, **kwargs):passclass NonCallableMock(Base):u"""不可以直接调用的mock类.""""""A non-callable version of `Mock`"""def __new__(cls, *args, **kw):u"""以name创建一个类,并返回新实例.所以mock对象直接不会相互影响."""# every instance has its own class# so we can create magic methods on the# class without stomping on other mocksnew = type(cls.__name__, (cls,), {'__doc__': cls.__doc__})instance = object.__new__(new)return instancedef __init__(self, spec=None, wraps=None, name=None, spec_set=None,parent=None, _spec_state=None, _new_name='', _new_parent=None,_spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs):if _new_parent is None:_new_parent = parent__dict__ = self.__dict____dict__['_mock_parent'] = parent__dict__['_mock_name'] = name__dict__['_mock_new_name'] = _new_name__dict__['_mock_new_parent'] = _new_parentif spec_set is not None:spec = spec_setspec_set = Trueif _eat_self is None:_eat_self = parent is not None# 添加spec mock方法self._mock_add_spec(spec, spec_set, _spec_as_instance, _eat_self)__dict__['_mock_children'] = {}__dict__['_mock_wraps'] = wraps__dict__['_mock_delegate'] = None__dict__['_mock_called'] = False__dict__['_mock_call_args'] = None__dict__['_mock_call_count'] = 0__dict__['_mock_call_args_list'] = _CallList()__dict__['_mock_mock_calls'] = _CallList()__dict__['method_calls'] = _CallList()__dict__['_mock_unsafe'] = unsafeif kwargs:# 配置mock对象self.configure_mock(**kwargs)_safe_super(NonCallableMock, self).__init__(spec, wraps, name, spec_set, parent,_spec_state)def attach_mock(self, mock, attribute):"""Attach a mock as an attribute of this one, replacing its name andparent. Calls to the attached mock will be recorded in the`method_calls` and `mock_calls` attributes of this one."""mock._mock_parent = Nonemock._mock_new_parent = Nonemock._mock_name = ''mock._mock_new_name = Nonesetattr(self, attribute, mock)def mock_add_spec(self, spec, spec_set=False):"""Add a spec to a mock. `spec` can either be an object or alist of strings. Only attributes on the `spec` can be fetched asattributes from the mock.If `spec_set` is True then only attributes on the spec can be set."""self._mock_add_spec(spec, spec_set)def _mock_add_spec(self, spec, spec_set, _spec_as_instance=False,_eat_self=False):u"""添加spec mock方法."""_spec_class = None_spec_signature = Noneif spec is not None and not _is_list(spec):if isinstance(spec, ClassTypes):_spec_class = specelse:_spec_class = _get_class(spec)# spec类的签名res = _get_signature_object(spec,_spec_as_instance, _eat_self)_spec_signature = res and res[1]spec = dir(spec)__dict__ = self.__dict____dict__['_spec_class'] = _spec_class__dict__['_spec_set'] = spec_set__dict__['_spec_signature'] = _spec_signature__dict__['_mock_methods'] = specdef __get_return_value(self):ret = self._mock_return_valueif self._mock_delegate is not None:ret = self._mock_delegate.return_valueif ret is DEFAULT:ret = self._get_child_mock(_new_parent=self, _new_name='()')self.return_value = retreturn retdef __set_return_value(self, value):if self._mock_delegate is not None:self._mock_delegate.return_value = valueelse:self._mock_return_value = value_check_and_set_parent(self, value, None, '()')__return_value_doc = "The value to be returned when the mock is called."return_value = property(__get_return_value, __set_return_v
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
