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实现rest请求api示例
Apr 22 Python
python实现矩阵乘法的方法
Jun 28 Python
Win10下Python环境搭建与配置教程
Nov 18 Python
Python队列的定义与使用方法示例
Jun 24 Python
用python写扫雷游戏实例代码分享
May 27 Python
如何爬取通过ajax加载数据的网站
Aug 15 Python
Python求两个字符串最长公共子序列代码实例
Mar 05 Python
通过python调用adb命令对App进行性能测试方式
Apr 23 Python
Pytorch高阶OP操作where,gather原理
Apr 30 Python
Python使用xlrd实现读取合并单元格
Jul 09 Python
Python实现机器学习算法的分类
Jun 03 Python
Python实现仓库管理系统
May 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基于GD库的缩略图生成代码(支持jpg,gif,png格式)
2014/06/19 PHP
PHP实现的数据对象映射模式详解
2019/03/20 PHP
js技巧--转义符&quot;\&quot;的妙用
2007/01/09 Javascript
javascript轻量级模板引擎juicer使用指南
2014/06/22 Javascript
通过JS判断联网类型和连接状态的实现代码
2015/04/01 Javascript
javascript Array 数组常用方法
2015/04/05 Javascript
JavaScript中操作字符串小结
2015/05/04 Javascript
基于jQuery.Hz2Py.js插件实现的汉字转拼音特效
2015/05/07 Javascript
JavaScript实现给按钮加上双重动作的方法
2015/08/14 Javascript
JS 面向对象之继承---多种组合继承详解
2016/07/10 Javascript
更靠谱的H5横竖屏检测方法(js代码)
2016/09/13 Javascript
jquery获取input type=text中的值的各种方式(总结)
2016/12/02 Javascript
基于vue-ssr服务端渲染入门详解
2018/01/08 Javascript
JavaScript 有用的代码片段和 trick
2018/02/22 Javascript
在Mac下彻底卸载node和npm的方法
2018/05/16 Javascript
解决LayUI数据表格复选框不居中显示的问题
2019/09/25 Javascript
js实现带箭头的进度流程
2020/03/26 Javascript
Python写的一个简单DNS服务器实例
2014/06/04 Python
django 自定义用户user模型的三种方法
2014/11/18 Python
Python 如何访问外围作用域中的变量
2016/09/11 Python
Django 前后台的数据传递的方法
2017/08/08 Python
Python通过matplotlib画双层饼图及环形图简单示例
2017/12/15 Python
Tensorflow 自带可视化Tensorboard使用方法(附项目代码)
2018/02/10 Python
用Python shell简化开发
2018/08/08 Python
对python多线程中Lock()与RLock()锁详解
2019/01/11 Python
Python爬虫实例——爬取美团美食数据
2020/07/15 Python
Python使用for生成列表实现过程解析
2020/09/22 Python
Perricone MD裴礼康美国官网:抗衰老护肤品
2016/09/26 全球购物
意大利巧克力店:Chocolate Shop
2019/07/24 全球购物
致长跑运动员广播稿
2014/01/31 职场文书
党性教育心得体会
2014/09/03 职场文书
2015年法制宣传月活动总结
2015/03/26 职场文书
2015驻村干部工作总结
2015/04/07 职场文书
起诉状范本
2015/05/20 职场文书
假期读书倡议书3篇
2019/08/19 职场文书
Java 超详细讲解hashCode方法
2022/04/07 Java/Android