Django项目test中的mock概述

本文环境python3.5.2

test中的mock实现

接口示例代码如下;

 ...# 路由配置('^api/business_application/?$', TestAPI.as_view()),...# 接口函数 app_name.apis.py
from rest_framework.generics import GenericAPIView
from rest_framework.response import Responsedef mock_test(test_val):print("test_mock_val")return "real_return" + test_valclass TestAPI(GenericAPIView):view_name = 'test'def get(self, request):data = mock_test("real")return Response({"detail": data})

test测试代码如下:

from unittest.mock import patch
from django.test import TestCaseclass MockAPITest(TestCase):
"""mock测试"""def setUp(self):self.url = reverse('app_name:test')@patch('app_name.apis.mock_test')def test_post(self, mock_test):mock_test.return_value = "mock"resp = self.client.get(self.url)retdata = resp.dataself.assertEqual(retdata['detail'], "mock")

此时在终端中,运行该用例测试结果正常,此时分析一下在该示例中mock的基本用法,由于本次测试的场景相对简单故在源码分析中,复杂的场景就不详细分析。

unittest.mock中的patch分析

首先查看patch函数的执行流程如下;

def _dot_lookup(thing, comp, import_path):try:return getattr(thing, comp)                             # 获取导入的属性except AttributeError:__import__(import_path)return getattr(thing, comp)def _importer(target):components = target.split('.')                              # 此时传入的target为app_name.apisimport_path = components.pop(0)                             # 获取app_namething = __import__(import_path)                             # 导入app_namefor comp in components:import_path += ".%s" % comp                             # 遍历路径名称thing = _dot_lookup(thing, comp, import_path)           # 依次获取导入的属性名称return thing                                                # 遍历完成后此时就获取到了 mock_test对应的方法属性def _get_target(target):try:target, attribute = target.rsplit('.', 1)               # 获取module路径 和方法名称,app_name.apis  和 mock_test方法  except (TypeError, ValueError):raise TypeError("Need a valid target to patch. You supplied: %r" %(target,))getter = lambda: _importer(target)                          # 封装成函数等待调用return getter, attribute                                    # 返回def patch(target, new=DEFAULT, spec=None, create=False,spec_set=None, autospec=None, new_callable=None, **kwargs):getter, attribute = _get_target(target)                     # 获取传入的属性对应的值return _patch(getter, attribute, new, spec, create,spec_set, autospec, new_callable, kwargs)

此时先去导入相关的module,去获取对应mock函数的函数,然后再传入_patch类中进行初始化,

class _patch(object):attribute_name = None_active_patches = []def __init__(self, getter, attribute, new, spec, create,spec_set, autospec, new_callable, kwargs):if new_callable is not None:if new is not DEFAULT:raise ValueError("Cannot use 'new' and 'new_callable' together")if autospec is not None:raise ValueError("Cannot use 'autospec' and 'new_callable' together")self.getter = getter                        # 属性获取对应self.attribute = attribute                  # 属性方法self.new = newself.new_callable = new_callableself.spec = specself.create = createself.has_local = Falseself.spec_set = spec_setself.autospec = autospecself.kwargs = kwargsself.additional_patchers = []def copy(self):patcher = _patch(self.getter, self.attribute, self.new, self.spec,self.create, self.spec_set,self.autospec, self.new_callable, self.kwargs)patcher.attribute_name = self.attribute_namepatcher.additional_patchers = [p.copy() for p in self.additional_patchers]return patcherdef __call__(self, func):if isinstance(func, type):                      # 在测试中调用的该方法return self.decorate_class(func)return self.decorate_callable(func)

由于先前博文已经分析过test的执行过程,此时test会执行到test(result)函数方法,由于本例中的test_post由装饰器patch修饰,此时返回的是_patch类的实例,当调用test_post方法时,就会调用__call__方法,由于此时test_post是一个call_func所以会执行到self.decorate_callable函数处,该函数如下;

def decorate_callable(self, func):if hasattr(func, 'patchings'):                                  # 检查func是否有patching属性,该属性为一个列表,该属性用于存放_patch对象,处理一个方法经过了多个_patch对象装饰func.patchings.append(self)                                 # 如果有则添加到该属性列表中return func                                                 # 返回该函数@wraps(func)def patched(*args, **keywargs):                                 # 当test真正执行时,调用该方法extra_args = []                                             # 传入参数entered_patchers = []exc_info = tuple()                                          # 报错信息try:for patching in patched.patchings:                      # 遍历patchings列表arg = patching.__enter__()                          # 调用_patch的__enter__方法entered_patchers.append(patching)                   # 添加到entered_patchers列表中if patching.attribute_name is not None:             # 检查_patch对应的需要mock的方法名是否为空keywargs.update(arg)                            # 不为空则更新传入参数elif patching.new is DEFAULT:                       extra_args.append(arg)                          # 检查传入的是否是默认参数,如果没有使用位置参数则使用位置参数args += tuple(extra_args)                               # 增加到位置参数中return func(*args, **keywargs)                          # 调用该test对应的方法,本例中为test_post方法except:if (patching not in entered_patchers and _is_started(patching)):                             # 报错处理# the patcher may have been started, but an exception# raised whilst entering one of its additional_patchersentered_patchers.append(patching)# Pass the exception to __exit__exc_info = sys.exc_info()# re-raise the exceptionraisefinally:for patching in reversed(entered_patchers):             # 执行正确最后执行_patch对应的__exit__方法patching.__exit__(*exc_info)patched.patchings = [self]                                      # 添加该属性return patched                                                  # 返回该patched方法

该函数主要就是执行了包装后的_patch方法,依次执行_patch的__enter__方法,当执行完成后,依次调用_patch的__exit__的方法,此时就依次完成对测试用例的执行,继续查看_patch对应的__enter__方法;

def get_original(self):target = self.getter()                                          # 获取命名空间的modulename = self.attribute                                           # 获取需要Mock的函数名original = DEFAULT                                              # 默认为DEFAULTlocal = False                                                   # 是否本地空间中找到 默认为Falsetry:original = target.__dict__[name]                            # 获取需要mock的函数,在本例中因为在同一命名空间中,是可以获取到该函数except (AttributeError, KeyError):original = getattr(target, name, DEFAULT)else:local = True                                                # 修改标志位if name in _builtins and isinstance(target, ModuleType):        # 判断是否为在内建名称中,判断target是否为moduleself.create = Trueif not self.create and original is DEFAULT:raise AttributeError("%s does not have the attribute %r" % (target, name))return original, local                                          # 返回原始函数,是否在本地命名空间找到标志def __enter__(self):"""Perform the patch."""new, spec, spec_set = self.new, self.spec, self.spec_setautospec, kwargs = self.autospec, self.kwargsnew_callable = self.new_callable                                # 获取初始化时传入的new_callable方法self.target = self.getter()                                     # 获取对应的moduel# normalise False to None                                       # 一般情况下设置为空或者Falseif spec is False:spec = Noneif spec_set is False:spec_set = Noneif autospec is False:autospec = Noneif spec is not None and autospec is not None:raise TypeError("Can't specify spec and autospec")if ((spec is not None or autospec is not None) andspec_set not in (True, None)):raise TypeError("Can't provide explicit spec_set *and* spec or autospec")original, local = self.get_original()                           # 获取被mock函数的原始函数,是否在module的命名空间中找到if new is DEFAULT and autospec is None:                         # 本例中new为DEFAULT autospec为Noneinherit = False                                             # 是否继承if spec is True:                                           # set spec to the object we are replacingspec = originalif spec_set is True:spec_set = originalspec = Noneelif spec is not None:if spec_set is True:spec_set = specspec = Noneelif spec_set is True:spec_set = originalif spec is not None or spec_set is not None:if original is DEFAULT:raise TypeError("Can't use 'spec' with create=True")if isinstance(original, type):# If we're patching out a class and there is a specinherit = TrueKlass = MagicMock                                               # 设置MagicMock类,该类就是替代mock的函数的类_kwargs = {}                                                    # 设置类的传入的位置参数if new_callable is not None:                                    # 如果在传入的时候指定了new_callable函数Klass = new_callable                                        # 使用传入的类作为mock的类实例elif spec is not None or spec_set is not None:this_spec = specif spec_set is not None:this_spec = spec_setif _is_list(this_spec):not_callable = '__call__' not in this_specelse:not_callable = not callable(this_spec)if not_callable:Klass = NonCallableMagicMockif spec is not None:_kwargs['spec'] = specif spec_set is not None:_kwargs['spec_set'] = spec_set# add a name to mocksif (isinstance(Klass, type) andissubclass(Klass, NonCallableMock) and self.attribute):         # 判断是否是类,是否是NonCallableMock子类,并且传入了mock的函数名称_kwargs['name'] = self.attribute                                # 设置名称为mock的函数名_kwargs.update(kwargs)                                              # 更新传入参数new = Klass(**_kwargs)                                              # 实例化一个实例if inherit and _is_instance_mock(new):# we can only tell if the instance should be callable if the# spec is not a listthis_spec = specif spec_set is not None:this_spec = spec_setif (not _is_list(this_spec) and not_instance_callable(this_spec)):Klass = NonCallableMagicMock_kwargs.pop('name')new.return_value = Klass(_new_parent=new, _new_name='()',**_kwargs)elif autospec is not None:# spec is ignored, new *must* be default, spec_set is treated# as a boolean. Should we check spec is not None and that spec_set# is a bool?if new is not DEFAULT:raise TypeError("autospec creates the mock for you. Can't specify ""autospec and new.")if original is DEFAULT:raise TypeError("Can't use 'autospec' with create=True")spec_set = bool(spec_set)if autospec is True:autospec = originalnew = create_autospec(autospec, spec_set=spec_set,_name=self.attribute, **kwargs)elif kwargs:# can't set keyword args when we aren't creating the mock# XXXX If new is a Mock we could call new.configure_mock(**kwargs)raise TypeError("Can't pass kwargs to a mock we aren't creating")new_attr = new                                                          # 赋值self.temp_original = original                                           # 保存旧函数到temp_original属性上self.is_local = local                                                   # 保存是否在本地找到标志位setattr(self.target, self.attribute, new_attr)                          # 设置属性值到module中,替换原来的属性值,此时本例中的mock_test就被替换为MagicMock类的实例if self.attribute_name is not None:                                     # 如果传入的属性值不为空extra_args = {}if self.new is DEFAULT:                                             # 判断是否为DEFAULTextra_args[self.attribute_name] =  new                          # 设置到传入参数中for patching in self.additional_patchers:arg = patching.__enter__()if patching.new is DEFAULT:extra_args.update(arg)return extra_argsreturn new

此时就将app_name.apis中的命名为mock_test的函数的名称替换为了对应的MagicMock类实例,此时在调用mock_test函数时,就是调用的MagicMock的__call__方法,在详细查看MagicMock的继承关系后,可以知道最终会调用到CallableMixin类的__call__方法,

def _mock_check_sig(self, *args, **kwargs):# stub method that can be replaced with one with a specific signaturepassdef __call__(_mock_self, *args, **kwargs):# can't use self in-case a function / method we are mocking uses self# in the signature_mock_self._mock_check_sig(*args, **kwargs)                 # 检查return _mock_self._mock_call(*args, **kwargs)               # 执行

此时继续查看_mock_call方法,由于本例中只涉及到了简单的情况,故复杂的业务场景没有详细分析;

def _mock_call(_mock_self, *args, **kwargs):self = _mock_selfself.called = Trueself.call_count += 1                                                    # 调用次数加1_new_name = self._mock_new_name                                         # 获取mock的名称_new_parent = self._mock_new_parent_call = _Call((args, kwargs), two=True)                                 # 包装传入参数self.call_args = _callself.call_args_list.append(_call)                           self.mock_calls.append(_Call(('', args, kwargs)))seen = set()skip_next_dot = _new_name == '()'do_method_calls = self._mock_parent is not Nonename = self._mock_namewhile _new_parent is not None:                                          # 如果_new_parent不为空this_mock_call = _Call((_new_name, args, kwargs))if _new_parent._mock_new_name:dot = '.'if skip_next_dot:dot = ''skip_next_dot = Falseif _new_parent._mock_new_name == '()':skip_next_dot = True_new_name = _new_parent._mock_new_name + dot + _new_nameif do_method_calls:if _new_name == name:this_method_call = this_mock_callelse:this_method_call = _Call((name, args, kwargs))_new_parent.method_calls.append(this_method_call)do_method_calls = _new_parent._mock_parent is not Noneif do_method_calls:name = _new_parent._mock_name + '.' + name_new_parent.mock_calls.append(this_mock_call)_new_parent = _new_parent._mock_new_parent# use ids here so as not to call __hash__ on the mocks_new_parent_id = id(_new_parent)if _new_parent_id in seen:breakseen.add(_new_parent_id)ret_val = DEFAULT                                                               # 返回值默认设置为DEFAULTeffect = self.side_effect                                                       # 获取effect值if effect is not None:if _is_exception(effect):raise effectif not _callable(effect):result = next(effect)if _is_exception(result):raise resultif result is DEFAULT:result = self.return_valuereturn resultret_val = effect(*args, **kwargs)if (self._mock_wraps is not None andself._mock_return_value is DEFAULT):return self._mock_wraps(*args, **kwargs)if ret_val is DEFAULT:                                                          # 判断ret_val是否为默认值ret_val = self.return_value                                                 # 如果是默认值则直接设置 为类的return_value值,在本例中在被设置为字符串"mock"return ret_val                                                                  # 返回 "mock"

此时该mock的函数就返回了在测试中设置的字符串"mock",此时在测试api执行的过程中就返回了该值。在__enter__执行完成,就完成了对Mock函数的最终的执行,在执行完成之后,还需要调用__exit__进行最终的相关属性的恢复;

def __exit__(self, *exc_info):"""Undo the patch."""if not _is_started(self):                       raise RuntimeError('stop called on unstarted patcher')if self.is_local and self.temp_original is not DEFAULT:                 # 还原相关属性,将被Mock的属性还原为真是的函数setattr(self.target, self.attribute, self.temp_original)else:delattr(self.target, self.attribute)                                # 先删除if not self.create and (not hasattr(self.target, self.attribute) orself.attribute in ('__doc__', '__module__','__defaults__', '__annotations__','__kwdefaults__')):# needed for proxy objects like django settingssetattr(self.target, self.attribute, self.temp_original)        # 再还原del self.temp_original                                                  # 删除相关属性值del self.is_localdel self.targetfor patcher in reversed(self.additional_patchers):if _is_started(patcher):patcher.__exit__(*exc_info)

至此,一个简单的测试的mock的过程就分析完成。本文基于了一个最简单的场景大致分析了该流程。

总结

本文简单的分析了mock该方法,在Django项目中的最简单的使用场景,mock的实现原理可以简单的描述为,通过导入相关模块,然后通过更新该模块对应的该函数的实例,通过该实例在被其他函数调用时,直接更改为我们期望的返回值,以此达到模拟假数据的返回,如有疏漏请批评指正。

Django项目test中的mock概述相关推荐

  1. Django项目开发——001如何学习django

    参考地址: https://code.ziqiangxuetang.com/django/django-tutorial.html https://www.django.cn/article/show ...

  2. Django项目日志概述

    Django项目日志概述 本文环境python3.5.2,Django版本1.10.2 Django项目中日志的实现 Django项目中使用的日志,使用了Python标准库中的logging模块进行实 ...

  3. 83.Django项目中使用验证码

    1. 概述 ​ 验证码(CAPTCHA)是"Completely Automated Public Turing test to tell Computers and Humans Apar ...

  4. 在ubuntu中使用cookiecutter搭建django项目时命令迟迟没有反应

    想在在ubuntu中使用cookiecutter搭建django项目,在官方文档中只是两行命令的事,没想到折腾了我几个小时. 问题描述 首先pip install cookiecutter==1.6. ...

  5. 创建python虚拟环境,安装django,创建一个django项目,在项目中创建一个应用(ubuntu16.04)...

    一.创建python虚拟环境 首先,为什么要创建python的虚拟环境? 因为,在实际的项目开发中,每个项目使用的框架库并不一样,或使用框架的版本不一样,这样需要 我们根据需求不断的更新或卸载相应的库 ...

  6. Diango博客--14.使用 Django 项目中的 ORM 编写伪造测试数据脚本

    文章目录 0.思路引导 1.脚本目录结构 2.使用 Faker 快速生成测试数据 3.批量生成测试数据 4.执行脚本 5.效果展示 0.思路引导 1)为了防止博客首页展示的文章过多以及提升加载速度,可 ...

  7. django 模板mysql_59 Django基础三件套 , 模板{{}}语言 , 程序连mysql Django项目app Django中ORM的使用...

    主要内容:https://www.cnblogs.com/liwenzhou/p/8688919.html 1 form表单中提交数据的三要素 a : form标签必须要有action和method的 ...

  8. python虚拟环境中安装diango_创建python虚拟环境,安装django,创建一个django项目,在项目中创建一个应用(ubuntu16.04)...

    一.创建python虚拟环境 首先,为什么要创建python的虚拟环境? 因为,在实际的项目开发中,每个项目使用的框架库并不一样,或使用框架的版本不一样,这样需要 我们根据需求不断的更新或卸载相应的库 ...

  9. Django项目中集成富文本编辑器的通用方法,适合KindEditor,xhEditor,NicEditor,wymeditor等 .

    首先,请参考我以前写的一篇博客:如何把nicEditor集成到django中使用 http://blog.csdn.net/huyoo/article/details/4382317 这篇文章中的做法 ...

最新文章

  1. 【数据挖掘笔记十】聚类分析:基本概念和方法
  2. PYG教程【三】对Cora数据集进行半监督节点分类
  3. Excel 转为 MySQL 语句
  4. c语言isfinite_csqrtf - [ C语言中文开发手册 ] - 在线原生手册 - php中文网
  5. 无需埋点的移动数据分析平台GrowingIO V1.0
  6. bootstraptable设置行高度_【短柱专题】窗台板为什么要通长设置
  7. 在Eclipse上使用egit插件通过ssh协议方式上传项目代码的具体步骤
  8. 混合汇编 src-control
  9. 制图折断线_CAD制图规范及技巧总结大全
  10. Matlab 简单图像分割实战
  11. Android TextToSpeech简单使用
  12. bootstrap分辨率
  13. poj_1363_Rai
  14. vue element Pagination分页组件二次封装
  15. 学会其中一个,轻松日入400+,今日头条隐藏的6大赚钱功能
  16. Linux网卡中断使单个CPU过载
  17. R 安装 “umap-learn“ python 包
  18. java基础语言+面向对象_经典案例——65个
  19. vue用户没有头像用姓名首字母做头像
  20. 点击查看全部图片(类似头条效果)

热门文章

  1. 对比四种爬虫定位元素方法,你更爱哪个?
  2. 想提前目睹人到中年的发型?试试这款自制秃头生成器
  3. 牛!Python 也能实现图像姿态识别溺水行为了!
  4. 从4个月到7天,Netflix开源Python框架Metaflow有何提升性能的魔法?
  5. TIOBE 1月编程语言排行榜:C语言再度「C 位」出道,Python惜败
  6. 实战:基于tensorflow 的中文语音识别模型 | CSDN博文精选
  7. 十大机器智能新型芯片:华为抢占一席,Google占比最多
  8. 谷歌、阿里们的杀手锏:三大领域,十大深度学习CTR模型演化图谱
  9. 继往开来!目标检测二十年技术综述
  10. 云从科技3D人体重建技术刷新3项纪录!仅凭照片即可生成精细模型