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正则表达式 re模块使用说明
May 19 Python
利用Python的Flask框架来构建一个简单的数字商品支付解决方案
Mar 31 Python
python实现数值积分的Simpson方法实例分析
Jun 05 Python
python操作excel的方法(xlsxwriter包的使用)
Jun 11 Python
python 一个figure上显示多个图像的实例
Jul 08 Python
Python中if有多个条件处理方法
Feb 26 Python
Django模板标签中url使用详解(url跳转到指定页面)
Mar 19 Python
让Django的BooleanField支持字符串形式的输入方式
May 20 Python
解决Keras 自定义层时遇到版本的问题
Jun 16 Python
如何设置PyCharm中的Python代码模版(推荐)
Nov 20 Python
python实现杨辉三角的几种方法代码实例
Mar 02 Python
python编程实现清理微信重复缓存文件
Nov 01 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
php实现简单爬虫的开发
2016/03/28 PHP
Laravel模型事件的实现原理详解
2018/03/14 PHP
php文件包含的几种方式总结
2019/09/19 PHP
JS在IE和FireFox之间常用函数的区别小结
2010/03/12 Javascript
jquery中ajax学习笔记4
2011/10/16 Javascript
jQuery Ajax异步处理Json数据详解
2013/11/05 Javascript
js中opener与parent的区别详细解析
2014/01/14 Javascript
javascript实现des解密加密全过程
2014/04/03 Javascript
js制作简易年历完整实例
2015/01/28 Javascript
js实现鼠标触发图片抖动效果的方法
2015/02/27 Javascript
原生JS实现层叠轮播图
2017/05/17 Javascript
angular directive的简单使用总结
2017/05/24 Javascript
JS实现经典的中国地区三级联动下拉菜单功能实例【测试可用】
2017/06/06 Javascript
js CSS3实现卡牌旋转切换效果
2017/07/04 Javascript
JavaScript Drum Kit 指南(纯 JS 模拟敲鼓效果)
2017/07/23 Javascript
微信小程序实现根据字母选择城市功能
2017/08/16 Javascript
javascript实现数字配对游戏的实例讲解
2017/12/14 Javascript
vue树形结构获取键值的方法示例
2018/06/21 Javascript
jQuery实现动态生成年月日级联下拉列表示例
2019/05/11 jQuery
[48:51]完美世界DOTA2联赛PWL S2 Magma vs InkIce 第一场 11.28
2020/12/02 DOTA
python基于socket实现网络广播的方法
2015/04/29 Python
Python实现KNN(K-近邻)算法的示例代码
2019/03/05 Python
python numpy数组中的复制知识解析
2020/02/03 Python
香港优质食材和美酒专门店:FoodWise
2017/09/01 全球购物
以下的初始化有什么区别
2013/12/16 面试题
应聘教师自荐信
2013/10/12 职场文书
外贸主管求职简历的自我评价
2013/10/23 职场文书
技术总监的工作职责
2013/11/13 职场文书
本科应届生求职信
2014/08/05 职场文书
农村党员学习党的群众路线教育实践活动心得体会
2014/11/04 职场文书
单方投资意向书
2015/05/11 职场文书
与死神共舞观后感
2015/06/15 职场文书
丧事答谢词大全
2015/09/30 职场文书
小学大队长竞选稿
2015/11/20 职场文书
提取视频中的音频 Python只需要三行代码!
2021/05/10 Python
Oracle中DBLink的详细介绍
2022/04/29 Oracle