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 22 Python
python2 与 python3 实现共存的方法
Jul 12 Python
Python做智能家居温湿度报警系统
Sep 25 Python
python使用adbapi实现MySQL数据库的异步存储
Mar 19 Python
python3中property使用方法详解
Apr 23 Python
Python树莓派学习笔记之UDP传输视频帧操作详解
Nov 15 Python
基于python-pptx库中文文档及使用详解
Feb 14 Python
python GUI库图形界面开发之PyQt5信号与槽事件处理机制详细介绍与实例解析
Mar 08 Python
解决TensorFlow程序无限制占用GPU的方法
Jun 30 Python
用python获取txt文件中关键字的数量
Dec 24 Python
Python数据类型最全知识总结
May 31 Python
Python音乐爬虫完美绕过反爬
Aug 30 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代码
2010/02/16 PHP
ThinkPHP中ajax使用实例教程
2014/08/22 PHP
PHP多进程编程实例详解
2017/07/19 PHP
在textarea中屏蔽js的某个function的javascript代码
2007/04/20 Javascript
JS中confirm,alert,prompt函数区别分析
2011/01/17 Javascript
js验证整数加保留小数点的简单实例
2013/12/02 Javascript
Extjs4中的分页应用结合前后台
2013/12/13 Javascript
借助JavaScript脚本判断浏览器Flash Player信息的方法
2014/07/09 Javascript
9款2014最热门jQuery实用特效推荐
2014/12/07 Javascript
jQuery+PHP实现可编辑表格字段内容并实时保存
2015/10/09 Javascript
jQuery Easyui使用(二)之可折叠面板动态加载无效果的解决方法
2016/08/17 Javascript
jQuery Checkbox 全选 反选的简单实例
2016/11/29 Javascript
Angularjs验证用户输入的字符串是否为日期时间
2017/06/01 Javascript
详解如何在vue项目中使用eslint+prettier格式化代码
2018/11/10 Javascript
JavaScript设计模式之观察者模式实例详解
2019/01/16 Javascript
vue+render+jsx实现可编辑动态多级表头table的实例代码
2020/04/01 Javascript
JQuery事件冒泡和默认行为代码实例
2020/05/13 jQuery
[03:42]2016国际邀请赛中国区预选赛首日现场玩家采访
2016/06/26 DOTA
Python遍历zip文件输出名称时出现乱码问题的解决方法
2015/04/08 Python
python通过imaplib模块读取gmail里邮件的方法
2015/05/08 Python
VSCode下好用的Python插件及配置
2018/04/06 Python
numpy中的高维数组转置实例
2018/04/17 Python
使用Python实现一个栈判断括号是否平衡
2018/08/23 Python
使用Python获取网段IP个数以及地址清单的方法
2018/11/01 Python
Django操作session 的方法
2020/03/09 Python
Python如何操作office实现自动化及win32com.client的运用
2020/04/01 Python
关于iframe跨域使用postMessage的实现
2019/10/29 HTML / CSS
严选全球尖货,立足香港:Bonpont宝盆
2018/07/24 全球购物
公共事业管理本科生求职信
2013/10/07 职场文书
争论的故事教学反思
2014/02/06 职场文书
人力资源总监工作说明
2014/03/03 职场文书
我们的节日清明节活动方案
2014/03/05 职场文书
星级党支部申报材料
2014/05/31 职场文书
学习焦裕禄同志为人民服务思想汇报
2014/09/10 职场文书
公司给客户的感谢信
2015/01/23 职场文书
Apache Linkis 中间件架构及快速安装步骤
2022/03/16 Servers