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 实现文件的递归拷贝实现代码
Aug 02 Python
Python with用法实例
Apr 14 Python
Python爬取国外天气预报网站的方法
Jul 10 Python
利用python代码写的12306订票代码
Dec 20 Python
Python遍历文件夹和读写文件的实现代码
Aug 28 Python
详解Python之数据序列化(json、pickle、shelve)
Mar 30 Python
Python之自动获取公网IP的实例讲解
Oct 01 Python
Python算法之求n个节点不同二叉树个数
Oct 27 Python
python 同时运行多个程序的实例
Jan 07 Python
Django框架使用mysql视图操作示例
May 15 Python
python实现ip地址查询经纬度定位详解
Aug 30 Python
用gpu训练好的神经网络,用tensorflow-cpu跑出错的原因及解决方案
Mar 03 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从身份证获取性别和出生年月
2017/02/09 PHP
实例讲解PHP页面静态化
2018/02/05 PHP
jquery判断单个复选框是否被选中的代码
2009/09/03 Javascript
始终在屏幕中间显示Div的代码(css+js)
2011/03/10 Javascript
用jquery存取照片的具体实现方法
2013/06/30 Javascript
基于jQuery实现的文字按钮表单特效整理
2014/12/07 Javascript
Javascript核心读书有感之词法结构
2015/02/01 Javascript
利用jquery实现下拉框的禁用与启用
2016/12/07 Javascript
javascript基础知识讲解
2017/01/11 Javascript
Vue.js 中的 $watch使用方法
2017/05/25 Javascript
JavaScript实现三级联动菜单效果
2017/08/16 Javascript
JS随机数产生代码分享
2018/02/24 Javascript
layui监听单元格编辑前后交互的例子
2019/09/16 Javascript
如何基于filter实现网站整体变灰功能
2020/04/17 Javascript
[01:50]WODOTA制作 DOTA2中文宣传片《HERO》
2013/04/28 DOTA
python单链表实现代码实例
2013/11/21 Python
在DigitalOcean的服务器上部署flaskblog应用
2015/12/19 Python
python清除字符串前后空格函数的方法
2018/10/21 Python
python多进程重复加载的解决方式
2019/12/13 Python
使用 pytorch 创建神经网络拟合sin函数的实现
2020/02/24 Python
python可视化分析的实现(matplotlib、seaborn、ggplot2)
2021/02/03 Python
详解CSS3开启硬件加速的使用和坑
2017/08/21 HTML / CSS
在阿联酋购买翻新手机和平板电脑:Teckzu
2021/02/12 全球购物
进修护士自我鉴定
2013/10/14 职场文书
军训的自我鉴定
2013/12/10 职场文书
学校师德师风整改方案
2014/10/28 职场文书
2015年文员个人工作总结
2015/04/09 职场文书
2015小学五年级班主任工作总结
2015/05/21 职场文书
鲁冰花观后感
2015/06/10 职场文书
学生会2016感恩节活动小结
2016/04/01 职场文书
2016年小学六一儿童节活动总结
2016/04/06 职场文书
2016年学校爱国卫生月活动总结
2016/04/06 职场文书
小学语文的各类谚语(70首)
2019/08/15 职场文书
实习员工转正的评语汇总,以备不时之需
2019/12/17 职场文书
Dubbo+zookeeper搭配分布式服务的过程详解
2022/04/03 Java/Android
python使用BeautifulSoup 解析HTML
2022/04/24 Python