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的高级Git库 Gittle
Sep 22 Python
Python最基本的数据类型以及对元组的介绍
Apr 14 Python
python中子类继承父类的__init__方法实例
Dec 15 Python
Python爬虫DNS解析缓存方法实例分析
Jun 02 Python
numpy.transpose对三维数组的转置方法
Apr 17 Python
Python3.6通过自带的urllib通过get或post方法请求url的实例
May 10 Python
Python基于滑动平均思想实现缺失数据填充的方法
Feb 21 Python
Python使用crontab模块设置和清除定时任务操作详解
Apr 09 Python
Pytorch 抽取vgg各层并进行定制化处理的方法
Aug 20 Python
使用python将最新的测试报告以附件的形式发到指定邮箱
Sep 20 Python
详解vscode实现远程linux服务器上Python开发
Nov 10 Python
selenium框架中driver.close()和driver.quit()关闭浏览器
Dec 08 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_screw 1.5:php加密: 安装与使用详解
2013/06/20 PHP
phpQuery占用内存过多的处理方法
2013/11/13 PHP
php上传功能集后缀名判断和随机命名(强力推荐)
2015/09/10 PHP
ThinkPHP中session函数详解
2016/09/14 PHP
Some tips of wmi scripting in jscript (1)
2007/04/03 Javascript
JavaScript 操作table,可以新增行和列并且隔一行换背景色代码分享
2013/07/05 Javascript
Jquery读取URL参数小例子
2013/08/30 Javascript
javascript拖拽上传类库DropzoneJS使用方法
2013/12/05 Javascript
js/jQuery简单实现选项卡功能
2014/01/02 Javascript
JavaScript中的值是按值传递还是按引用传递问题探讨
2015/01/30 Javascript
JS弹出可拖拽可关闭的div层完整实例
2015/02/13 Javascript
如何使用jQuery技术开发ios风格的页面导航菜单
2015/07/29 Javascript
详解在Vue中如何使用axios跨域访问数据
2017/07/07 Javascript
详解vue项目首页加载速度优化
2017/10/18 Javascript
Vue全家桶实践项目总结(推荐)
2017/11/04 Javascript
当vue路由变化时,改变导航栏的样式方法
2018/08/22 Javascript
Element-UI中Upload上传文件前端缓存处理示例
2019/02/21 Javascript
防止Layui form表单重复提交的实现方法
2019/09/10 Javascript
jquery实现吸顶导航效果
2020/01/08 jQuery
对python添加模块路径的三种方法总结
2018/10/16 Python
使用Windows批处理和WMI设置Python的环境变量方法
2019/08/14 Python
在Matplotlib图中插入LaTex公式实例
2020/04/17 Python
Django models文件模型变更错误解决
2020/05/11 Python
Python爬虫简单运用爬取代理IP的实现
2020/12/01 Python
基于 HTML5 Canvas实现 的交互式地铁线路图
2018/03/05 HTML / CSS
美丽的现代设计家具:2Modern
2018/07/26 全球购物
大码女装:Ulla Popken
2019/08/06 全球购物
如何写出高质量、高性能的MySQL查询
2014/11/17 面试题
留学推荐信怎么写
2014/01/25 职场文书
酒店保安员岗位职责
2014/01/31 职场文书
大学生党员承诺书
2014/05/20 职场文书
合作协议书模板2014
2014/09/26 职场文书
个人查摆问题及整改措施
2014/10/16 职场文书
鲁冰花观后感
2015/06/10 职场文书
文化苦旅读书笔记
2015/06/29 职场文书
Python词云的正确实现方法实例
2021/05/08 Python