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制作简单的钢琴程序的教程
Apr 01 Python
NumPy 如何生成多维数组的方法
Feb 05 Python
Python绘制的二项分布概率图示例
Aug 22 Python
python找出完数的方法
Nov 12 Python
Python装饰器基础概念与用法详解
Dec 22 Python
详解python3安装pillow后报错没有pillow模块以及没有PIL模块问题解决
Apr 17 Python
python对绑定事件的鼠标、按键的判断实例
Jul 17 Python
python实现从wind导入数据
Dec 03 Python
基于python+selenium的二次封装的实现
Jan 06 Python
python 日志 logging模块详细解析
Mar 31 Python
pytorch MSELoss计算平均的实现方法
May 12 Python
Python作用域和名称空间的详细介绍
Apr 13 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二维数组转成字符串示例
2014/02/17 PHP
基于JQuery+PHP编写砸金蛋中奖程序
2015/09/08 PHP
原生js实现shift/ctrl/alt按键的获取
2013/04/08 Javascript
iframe子父页面调用js函数示例
2013/11/07 Javascript
多种方法实现360浏览器下禁止自动填写用户名密码
2014/06/16 Javascript
深入分析jquery解析json数据
2014/12/09 Javascript
JavaScript设置获取和设置属性的方法
2015/03/04 Javascript
浅谈下拉菜单中的Option对象
2015/05/10 Javascript
jQuery实现的登录浮动框效果代码
2015/09/26 Javascript
JavaScript中三种异步上传文件方式
2016/03/06 Javascript
easyui form validate总是返回false的原因及解决方法
2016/11/07 Javascript
nodejs连接mongodb数据库实现增删改查
2016/12/01 NodeJs
详解react使用react-bootstrap当轮子造车
2017/08/15 Javascript
解读vue生成的文件目录结构及说明
2017/11/27 Javascript
webpack打包并将文件加载到指定的位置方法
2018/02/22 Javascript
详解微信小程序调起键盘性能优化
2018/07/24 Javascript
vue调用语音播放的方法
2019/09/27 Javascript
编写Python脚本批量下载DesktopNexus壁纸的教程
2015/05/06 Python
详解Django通用视图中的函数包装
2015/07/21 Python
详谈python3中用for循环删除列表中元素的坑
2018/04/19 Python
python 寻找list中最大元素对应的索引方法
2018/06/28 Python
Python实现的线性回归算法示例【附csv文件下载】
2018/12/29 Python
详解重置Django migration的常见方式
2019/02/15 Python
Python字符串逆序输出的实例讲解
2019/02/16 Python
世界著名的顶级牛排:Omaha Steak(奥马哈牛排)
2016/09/20 全球购物
美国受欢迎的眼影品牌:BH Cosmetics
2016/10/25 全球购物
美国网上眼镜供应商:LEOTONY(眼镜、RX太阳镜和太阳镜)
2017/10/31 全球购物
英国豪华家具和家居用品购物网站:Teddy Beau
2020/10/12 全球购物
信访工作者先进事迹
2014/01/17 职场文书
入伍通知书
2015/04/23 职场文书
2015年骨干教师工作总结
2015/05/26 职场文书
2016年记者节感言
2015/12/08 职场文书
JS新手入门数组处理的实用方法汇总
2021/04/07 Javascript
一篇文章弄懂Python中的内建函数
2021/08/07 Python
Oracle中update和select 关联操作
2022/01/18 Oracle
ICOM R71E和R72E图文对比解说
2022/04/07 无线电