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 21 Python
Python中import导入上一级目录模块及循环import问题的解决
Jun 04 Python
python xml解析实例详解
Nov 14 Python
详解Python实现多进程异步事件驱动引擎
Aug 25 Python
python简单实例训练(21~30)
Nov 15 Python
利用python编写一个图片主色转换的脚本
Dec 07 Python
使用k8s部署Django项目的方法步骤
Jan 14 Python
Python任意字符串转16, 32, 64进制的方法
Jun 12 Python
python实现五子棋人机对战游戏
Mar 25 Python
python爬虫的一个常见简单js反爬详解
Jul 09 Python
Python API自动化框架总结
Nov 12 Python
Python爬虫:从m3u8文件里提取小视频的正确操作
May 14 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与javascript对多项选择的处理
2006/10/09 PHP
PHP 根据IP地址控制访问的代码
2010/04/22 PHP
php 批量替换html标签的实例代码
2013/11/26 PHP
微信支付开发教程(一)微信支付URL配置
2014/05/28 PHP
将CMYK颜色值和RGB颜色相互转换的PHP代码
2014/07/28 PHP
location.search在客户端获取Url参数的方法
2010/06/08 Javascript
扩展jquery实现客户端表格的分页、排序功能代码
2011/03/16 Javascript
javascript动画对象支持加速、减速、缓入、缓出的实现代码
2012/09/30 Javascript
js星星评分效果
2014/07/24 Javascript
javascript 判断整数方法分享
2014/12/16 Javascript
input点击后placeholder中的提示消息消失
2016/01/15 Javascript
JS弹出层遮罩,隐藏背景页面滚动条细节优化分析
2016/04/29 Javascript
快速掌握jQuery插件WebUploader文件上传
2016/11/07 Javascript
jquery插件bootstrapValidator数据验证详解
2016/11/09 Javascript
canvas 画布在主流浏览器中的尺寸限制详细介绍
2016/12/15 Javascript
详解用vue.js和laravel实现微信授权登陆
2017/06/23 Javascript
基于zepto.js实现手机相册功能
2017/07/11 Javascript
jquery实现左右轮播切换效果
2018/01/01 jQuery
Vue项目中跨域问题解决方案
2018/06/05 Javascript
React router动态加载组件之适配器模式的应用详解
2018/09/12 Javascript
微信公众号H5支付接口调用方法
2019/01/10 Javascript
Python字符串通过'+'和join函数拼接新字符串的性能测试比较
2019/03/05 Python
Python使用pandas和xlsxwriter读写xlsx文件的方法示例
2019/04/09 Python
Python数据可视化实现正态分布(高斯分布)
2019/08/21 Python
python随机生成大小写字母数字混合密码(仅20行代码)
2020/02/01 Python
如何基于python对接钉钉并获取access_token
2020/04/21 Python
Pycharm插件(Grep Console)自定义规则输出颜色日志的方法
2020/05/27 Python
怎么解决pycharm license Acti的方法
2020/10/28 Python
如何使用css3实现一个类在线直播的队列动画的示例代码
2020/06/17 HTML / CSS
html5表单及新增的改良元素详解
2016/06/07 HTML / CSS
Python面试题:如何用Python来发送邮件
2016/03/15 面试题
给导游的表扬信
2014/01/10 职场文书
酒店开业庆典主持词
2014/03/21 职场文书
求职者怎样写自荐信
2014/04/13 职场文书
2015暑假打工实践报告
2015/07/13 职场文书
pandas中DataFrame检测重复值的实现
2021/05/26 Python