最近在搞软工项目的后端测试,重新复习了一下python的mock.patch,并用它简化了对一些复杂逻辑的测试,在此记录

问题描述

本组的项目比较特殊,设计对教务网站的模拟登陆与信息爬取,同时不少接口会有发送邮件的side-effect。在自动化测试时,由于这两个功能的行为与生产环境的真实数据(用户的教务账号、邮箱地址)耦合,需要想办法设计专门的测试流程。容易想到的比较简单的思路有:

为相关接口开一个标记测试的布尔值参数,在测试时传入,屏蔽邮件发送/爬取教务网站的相关逻辑,并为邮件/爬虫设计单独的测试逻辑,将其与网站主要逻辑的测试解耦。好处是实现简单,缺点是需要修改正常的接口逻辑,不符合开闭原则,且若处理不当易导致安全隐患。

提供一个专门的测试账号,在测试时使用该账号测试相关功能。优点是不需要修改接口逻辑,问题是对于爬取教务这种需求,提供的账号是真实的学生账号,自动测试时可预见的频繁密集的数据请求可能会影响账号的正常使用。

综合上述两个思路,不难想到去寻找一种可以跳过邮件发送/网站爬取逻辑但又不需要修改后端代码逻辑的方法。由于python是解释型语言,在程序运行时可以非常方便地将某一段代码进行动态替换,所以只要在测试时将发送邮件的函数/方法替换成一个“假”函数即可。借助importlib等手段不难实现,但工作量稍大,实际上python已经为我们提供了unittest.mock.patch来满足这种需求。

基本介绍

详细使用请见官方文档

这里总结一些快速上手的要点

使用方式:装饰器或上下文管理器

首先我们给出一个玩具函数func_to_test,这个函数接收两个参数和一个可选参数,返回两个参数的加和,并打印可选参数的值

# author : Mistariano (hdl730@163.com)

# file path : pack1/my_module.py

# module name : pack1.my_module

def verbose_adder(arg1, arg2, kwarg1='default'):

print(kwarg1) # side-effect

return arg1 + arg2 # post-condition

def func_to_test():

return verbose_adder(10, 10)

现在我们希望借助patch,hack掉verbose_adder这个函数。希望无论测试时func_to_test实际传给verbose_adder的参数是什么,其返回值都为3,同时输出一行特定的信息。

下述两种写法都是可行的

# author : Mistariano (hdl730@163.com)

from pack1.my_module import func_to_test

from unittest import mock

def print_test_info(*args, **kwargs):

print('this is a microphone check.')

print('arguments:', args, kwargs)

return mock.DEFAULT # NOTICE here

@mock.patch('pack1.my_module.verbose_adder')

def test_func_to_test__decorator(mock_obj):

mock_obj.return_value = 3

mock_obj.side_effect = print_test_info

assert func_to_test() == 3

def test_func_to_test__context():

with mock.patch('pack1.my_module.verbose_adder') as mock_obj:

mock_obj.return_value = 3

mock_obj.side_effect = print_test_info

assert func_to_test() == 3

可以看到,mock.patch可以以函数装饰器的方式或上下文管理器的方式使用,前者需要被装饰的函数提供一个额外的参数接收mock对象实例mock_obj,后者则会将mock对象实例作为上下文管理器的返回值。当然,直接将其作为函数调用也是可取的,但个人并不推荐,这里不详细讨论。

通过为mock_obj指定返回值(可选的)与副作用(也是可选的)来定制mock函数的行为,从而实现对原函数/方法的动态覆盖

注意到用来作为mock对象side_effect的回调函数返回值是mock.DEFAULT,这样写是为了避免覆盖另行制定的return_value

应该给哪个函数打patch

Mock an item where it is used, not where it came from.

python的加载机制很有意思。对于一个函数,如果mock中指定的模块路径是它定义的地方(而不是实际被调用的地方),则mock可能无法成功覆盖已经加载了这个函数的其它模块

对这个问题的详细解释可以参考官方文档,同时这个Stackoverflow提问给出了一些例子,有助于进一步理解。

实战

这里直接给出本组软工代码中使用patch覆盖邮件发送及教务爬取的代码段

from django.test import TestCase

from unittest import mock

# ...

class ViewTestCases(TestCase):

# ...

@staticmethod

def mock_mail_send(*args, **kwargs):

print('sending mock mail.. args:', args, kwargs)

return mock.DEFAULT

@staticmethod

def mock_update_from_course(*args, **kwargs):

print('mock updating course... args:', args, kwargs)

return mock.DEFAULT

def _test_req_context(self, func, exp_code, auth_required):

def test_req_wrapper(*args, **kwargs):

token = None if not self._user_data else self._user_data['token']

with mock.patch('ddl_killer.utils.sendmail.YAG.send') as mail_obj:

mail_obj.side_effect = self.mock_mail_send

with mock.patch('ddl_killer.views.updateFromCourse') as mock_course:

mock_course.side_effect = self.mock_update_from_course

mock_course.return_value = self.TEST_COURSE

if auth_required:

r_data = func(*args, HTTP_AUTHORIZATION=None, **kwargs).json()

self.assertEqual(r_data['code'], 401, r_data)

r_data = func(*args, HTTP_AUTHORIZATION=token, **kwargs).json()

self.assertEqual(r_data['code'], exp_code)

return r_data

return test_req_wrapper

def post(self, *args, exp_code=200, auth_required=True, **kwargs):

return self._test_req_context(self._client.post, exp_code, auth_required)(*args, **kwargs)

def get(self, *args, exp_code=200, auth_required=True, **kwargs):

return self._test_req_context(self._client.get, exp_code, auth_required)(*args, **kwargs)

def _login(self):

if self._user_data is None:

print('logging...')

r = self.post('/api/login',

{'uid': self.TEST_USER_ID,

'password': self.password_encrypt},

auth_required=False)

self._user_data = r

def test_show_user(self):

self._login()

data = self.post('/api/user/{}/info'.format(self.TEST_USER_ID))

self.assertEqual(data['uid'], self.TEST_USER_ID)

self.assertEqual(data['name'], self.TEST_USER_NAME)

self.assertEqual(data['email'], self.TEST_USER_EMAIL)

def test_user_login_not_activated(self):

self._user_orm.is_active = False

self._user_orm.save()

r = self.post('/api/login', {'uid': self.TEST_USER_ID,

'password': self.password_encrypt},

exp_code=400,

auth_required=False)

self._user_orm.is_active = True

self._user_orm.save()

def test_edit_user(self):

self._login()

data = self.post('/api/modify', {

'uid': self.TEST_USER_ID,

'name': self.TEST_USER_NAME,

'password': '',

'email': 'tmp_email@mail.com'

})

self.assertEqual(User.objects.get(uid=self.TEST_USER_ID).email,

'tmp_email@mail.com')

self._user_orm.email = self.TEST_USER_EMAIL

self._user_orm.save()

# ...

python mock patch_偷梁换柱:使用mock.patch辅助python单元测试相关推荐

  1. python答题系统的代码_答题辅助python代码实现

    本文实例为大家分享了答题辅助python具体代码,供大家参考,具体内容如下 from screenshot import pull_screenshot import time, urllib.req ...

  2. python实现选择题自动答题_答题辅助python代码实现

    本文实例为大家分享了答题辅助python具体代码,供大家参考,具体内容如下 from screenshot import pull_screenshot import time, urllib.req ...

  3. 微信跳一跳python全部代码_微信跳一跳辅助python代码实现

    微信跳一跳辅助的python具体实现代码,供大家参考,具体内容如下 这是一个 2.5D 插画风格的益智游戏,玩家可以通过按压屏幕时间的长短来控制这个「小人」跳跃的距离.可能刚开始上手的时候,因为时间距 ...

  4. mockito mock void方法_使用 Junit + Mockito 实践单元测试!

    一.前言 相信做过开发的同学,都多多少少写过下面的代码,很长一段时间我一直以为这就是单元测试... @SpringBootTest @RunWith(SpringRunner.class) publi ...

  5. mock模拟接口测试 vue_Easy Mock以及Vue+Mock.js模拟数据

    Easy Mock以及Vue+Mock.js模拟数据 一.Mock.js简介 Mock.js是一个可以模拟后端数据,也可以模拟增删改查操作的js库 基础语法规范 数据模板中的每个属性由 3 部分构成: ...

  6. python+opencv别踩白块儿游戏辅助,一天一个opencv小项目(已开源)

    python+opencv别踩白块儿游戏辅助,一天一个opencv小项目(已开源) 见链接

  7. python微信公众号秒杀代码_微信跳一跳辅助python代码实现

    微信跳一跳辅助python代码实现 来源:中文源码网    浏览: 次    日期:2018年9月2日 [下载文档:  微信跳一跳辅助python代码实现.txt ] (友情提示:右键点上行txt文档 ...

  8. Mockito3.8 如何mock静态方法 (如何mock PageHelper)

    项目中遇到需要mock PageHelper,因为用到了startPage方法,而此方法是静态方法,如果需要mock静态方法,网上说法比较多的都是需要用Powermock,而这就需要引入新的依赖,这样 ...

  9. python好用的软件_【分享|10款超好用的辅助Python的软件,初学者请查收!】- 环球网校...

    [摘要]在这个科学技术高速发展的时代,越来越多的人都开始选择学习编程软件,那么首先被大家选择的编程软件就是python,也用在各行各业之中,并被大家所熟知,所以也有越来越多的python学习者关注Py ...

最新文章

  1. 同义词词林 java_基于同义词词林扩展版的词语相似度计算
  2. pyMagic:用python控制的Geek入门神器
  3. 写个函数用来对二维数组排序
  4. 米其林全球挑战赛电子门票欣赏
  5. 面向.NET开发人员的Dapr——参考应用程序
  6. html flash层级,解决FLASH遮住其他层元素问题
  7. StarCraft的工程师谈美国的游戏开发过程
  8. 成功走职场要找准自己的快捷键
  9. WordPress目录解析
  10. 计算机钢琴键盘,Everyone Piano
  11. css图形动画,CSS3 实现图形下落动画效果
  12. 从控制台输入用户名和密码, 然后 判断输入的用户名是否是@“Frank”, 密码 是否是 @“lanou”, 如果用户名和密码都正确,则输出登录成功, 否则输出登录失败. 提示:
  13. linux批量管理工具之ansible
  14. IDEA 2021.2 取消 双击shift 全局搜索
  15. 基于 Paraview 扩展与实现——(1)
  16. 迎接爆炸主升浪?(附股)
  17. ArcGIS平滑处理
  18. 中国香茅醇行业研究与投资预测报告(2022版)
  19. 个人隐私保护7:真正进入加密U盘实战3(制作篇)
  20. BP神经网络预测回归MATLAB代码(代码完整可直接用,注释详细,可供学习)

热门文章

  1. 使用GDI绘制像素矩阵与像素缓冲区
  2. phpStorm中使用模板快速创建html基本网页代码
  3. ORA-28000 账号被锁定的解决办法
  4. 由感而发:离职的第四个理由
  5. Matlab fplot函数详解
  6. 了解node.js模块化和npm包
  7. 有哪些一般人不知道的数据获取方式
  8. 计算机三级有必要考吗?计算机三级有哪些科目?
  9. 阿里云服务器实时计算Flink/Blink首选大数据型d2c、d2s实例
  10. 【转】如何读学术论文