Python如何在单元测试中给对象打补丁


Posted in Python onAugust 03, 2020

问题

你写的单元测试中需要给指定的对象打补丁, 用来断言它们在测试中的期望行为(比如,断言被调用时的参数个数,访问指定的属性等)。

解决方案

unittest.mock.patch() 函数可被用来解决这个问题。 patch() 还可被用作一个装饰器、上下文管理器或单独使用,尽管并不常见。 例如,下面是一个将它当做装饰器使用的例子:

from unittest.mock import patch
import example

@patch('example.func')
def test1(x, mock_func):
  example.func(x)    # Uses patched example.func
  mock_func.assert_called_with(x)

它还可以被当做一个上下文管理器:

with patch('example.func') as mock_func:
  example.func(x)   # Uses patched example.func
  mock_func.assert_called_with(x)

最后,你还可以手动的使用它打补丁:

p = patch('example.func')
mock_func = p.start()
example.func(x)
mock_func.assert_called_with(x)
p.stop()

如果可能的话,你能够叠加装饰器和上下文管理器来给多个对象打补丁。例如:

@patch('example.func1')
@patch('example.func2')
@patch('example.func3')
def test1(mock1, mock2, mock3):
  ...

def test2():
  with patch('example.patch1') as mock1, \
     patch('example.patch2') as mock2, \
     patch('example.patch3') as mock3:
  ...

讨论

patch() 接受一个已存在对象的全路径名,将其替换为一个新的值。 原来的值会在装饰器函数或上下文管理器完成后自动恢复回来。 默认情况下,所有值会被 MagicMock 实例替代。例如:

>>> x = 42
>>> with patch('__main__.x'):
...   print(x)
...
<MagicMock name='x' id='4314230032'>
>>> x
42
>>>

不过,你可以通过给 patch() 提供第二个参数来将值替换成任何你想要的:

>>> x
42
>>> with patch('__main__.x', 'patched_value'):
...   print(x)
...
patched_value
>>> x
42
>>>

被用来作为替换值的 MagicMock 实例能够模拟可调用对象和实例。 他们记录对象的使用信息并允许你执行断言检查,例如:

>>> from unittest.mock import MagicMock
>>> m = MagicMock(return_value = 10)
>>> m(1, 2, debug=True)
10
>>> m.assert_called_with(1, 2, debug=True)
>>> m.assert_called_with(1, 2)
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File ".../unittest/mock.py", line 726, in assert_called_with
  raise AssertionError(msg)
AssertionError: Expected call: mock(1, 2)
Actual call: mock(1, 2, debug=True)
>>>

>>> m.upper.return_value = 'HELLO'
>>> m.upper('hello')
'HELLO'
>>> assert m.upper.called

>>> m.split.return_value = ['hello', 'world']
>>> m.split('hello world')
['hello', 'world']
>>> m.split.assert_called_with('hello world')
>>>

>>> m['blah']
<MagicMock name='mock.__getitem__()' id='4314412048'>
>>> m.__getitem__.called
True
>>> m.__getitem__.assert_called_with('blah')
>>>

一般来讲,这些操作会在一个单元测试中完成。例如,假设你已经有了像下面这样的函数:

# example.py
from urllib.request import urlopen
import csv

def dowprices():
  u = urlopen('http://finance.yahoo.com/d/quotes.csv?s=@^DJI&f=sl1')
  lines = (line.decode('utf-8') for line in u)
  rows = (row for row in csv.reader(lines) if len(row) == 2)
  prices = { name:float(price) for name, price in rows }
  return prices

正常来讲,这个函数会使用 urlopen() 从Web上面获取数据并解析它。 在单元测试中,你可以给它一个预先定义好的数据集。下面是使用补丁操作的例子:

import unittest
from unittest.mock import patch
import io
import example

sample_data = io.BytesIO(b'''\
"IBM",91.1\r
"AA",13.25\r
"MSFT",27.72\r
\r
''')

class Tests(unittest.TestCase):
  @patch('example.urlopen', return_value=sample_data)
  def test_dowprices(self, mock_urlopen):
    p = example.dowprices()
    self.assertTrue(mock_urlopen.called)
    self.assertEqual(p,
             {'IBM': 91.1,
             'AA': 13.25,
             'MSFT' : 27.72})

if __name__ == '__main__':
  unittest.main()

本例中,位于 example 模块中的 urlopen() 函数被一个模拟对象替代, 该对象会返回一个包含测试数据的 ByteIO()

还有一点,在打补丁时我们使用了 example.urlopen 来代替 urllib.request.urlopen 。 当你创建补丁的时候,你必须使用它们在测试代码中的名称。 由于测试代码使用了 from urllib.request import urlopen ,那么 dowprices() 函数 中使用的 urlopen() 函数实际上就位于 example 模块了。

本节实际上只是对 unittest.mock 模块的一次浅尝辄止。 更多更高级的特性,请参考 官方文档

以上就是Python如何在单元测试中给对象打补丁的详细内容,更多关于Python 单元测试的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python使用socket向客户端发送数据的方法
Apr 29 Python
分享一下Python 开发者节省时间的10个方法
Oct 02 Python
python字典多键值及重复键值的使用方法(详解)
Oct 31 Python
python 定时器,轮询定时器的实例
Feb 20 Python
Django xadmin开启搜索功能的实现
Nov 15 Python
python 求10个数的平均数实例
Dec 16 Python
selenium+Chrome滑动验证码破解二(某某网站)
Dec 17 Python
学习Python列表的基础知识汇总
Mar 10 Python
python 浮点数四舍五入需要注意的地方
Aug 18 Python
PyTorch中Tensor的数据类型和运算的使用
Sep 03 Python
Python 中 sorted 如何自定义比较逻辑
Feb 02 Python
使用python如何删除同一文件夹下相似的图片
May 07 Python
Python 数据的累加与统计的示例代码
Aug 03 #Python
Python 爬虫性能相关总结
Aug 03 #Python
python接口自动化之ConfigParser配置文件的使用详解
Aug 03 #Python
Python 利用OpenCV给照片换底色的示例代码
Aug 03 #Python
Python3基于plotly模块保存图片表格
Aug 03 #Python
详解Python的爬虫框架 Scrapy
Aug 03 #Python
Python利用Faiss库实现ANN近邻搜索的方法详解
Aug 03 #Python
You might like
用libtemplate实现静态网页生成
2006/10/09 PHP
php学习之数据类型之间的转换介绍
2011/06/09 PHP
关于svn冲突的解决方法
2013/06/21 PHP
PHP图片裁剪函数(保持图像不变形)
2014/05/04 PHP
thinkphp修改配置进入默认首页的方法
2017/02/07 PHP
三个思路解决laravel上传文件报错:413 Request Entity Too Large问题
2017/11/13 PHP
php精度计算的问题解析
2019/06/21 PHP
javascript学习笔记(九)javascript中的原型(prototype)及原型链的继承方式
2011/04/12 Javascript
Javascript实现的类似Google的Div拖动效果代码
2011/08/09 Javascript
Javascript 面向对象编程(一) 封装
2011/08/28 Javascript
jQuery常见开发技巧详细整理
2013/01/02 Javascript
JQUERY对单选框(radio)操作的小例子
2013/04/25 Javascript
js下拉框二级关联菜单效果代码具体实现
2013/08/03 Javascript
jQuery中last()方法用法实例
2015/01/06 Javascript
jQuery插件实现多级联动菜单效果
2015/12/01 Javascript
Angularjs修改密码的实例代码
2017/05/26 Javascript
jQuery实现合并表格单元格中相同行操作示例
2019/01/28 jQuery
详解Vue前端生产环境发布配置实战篇
2019/05/07 Javascript
node命令行工具之实现项目工程自动初始化的标准流程
2019/08/12 Javascript
uniapp微信小程序实现一个页面多个倒计时
2020/11/01 Javascript
[58:57]2018DOTA2亚洲邀请赛3月29日小组赛B组 Effect VS VGJ.T
2018/03/30 DOTA
[50:21]Liquid vs Winstrike 2018国际邀请赛小组赛BO2 第二场
2018/08/19 DOTA
Python 连连看连接算法
2008/11/22 Python
python与caffe改变通道顺序的方法
2018/08/04 Python
python通过robert、sobel、Laplace算子实现图像边缘提取详解
2019/08/21 Python
python 经典数字滤波实例
2019/12/16 Python
Python基于stuck实现scoket文件传输
2020/04/02 Python
python将下载到本地m3u8视频合成MP4的代码详解
2020/11/24 Python
Kappa英国官方在线商店:服装和运动器材
2020/11/22 全球购物
Tomcat的缺省是多少,怎么修改
2014/04/09 面试题
电子商务个人自荐信
2013/12/12 职场文书
公司前台接待岗位职责
2015/04/03 职场文书
干货:如何写好观后感 !
2019/05/21 职场文书
Python学习开发之图形用户界面详解
2021/08/23 Python
vue使用echarts实现折线图
2022/03/21 Vue.js
Linux安装Docker详细教程
2022/07/07 Servers