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抓取京东价格分析京东商品价格走势
Jan 09 Python
Python实现简易端口扫描器代码实例
Mar 15 Python
PyQt5每天必学之滑块控件QSlider
Apr 20 Python
python同时遍历数组的索引和值的实例
Nov 15 Python
python实现二维插值的三维显示
Dec 17 Python
python画图--输出指定像素点的颜色值方法
Jul 03 Python
python正则表达式匹配不包含某几个字符的字符串方法
Jul 23 Python
在django中,关于session的通用设置方法
Aug 06 Python
python开头的coding设置方法
Aug 08 Python
Django生成数据库及添加用户报错解决方案
Oct 09 Python
python3实现语音转文字(语音识别)和文字转语音(语音合成)
Oct 14 Python
用python读取xlsx文件
Dec 17 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中的错误处理、异常处理机制分析
2012/05/07 PHP
php中长文章分页显示实现代码
2012/09/29 PHP
php 批量生成html,txt文件的实现代码
2013/06/26 PHP
php中的boolean(布尔)类型详解
2013/10/28 PHP
php通过baihui网API实现读取word文档并展示
2015/06/22 PHP
php intval函数用法总结
2019/04/14 PHP
PHP代码覆盖率统计详解
2020/07/22 PHP
用JavaScript实现UrlEncode和UrlDecode的脚本代码
2008/07/23 Javascript
Javascript创建自定义对象 创建Object实例添加属性和方法
2012/06/04 Javascript
javascript检测页面是否缩放的小例子
2013/05/16 Javascript
javascript loadScript异步加载脚本示例讲解
2013/11/14 Javascript
JS和css实现检测移动设备方向的变化并判断横竖屏幕
2015/05/25 Javascript
浅析javascript中的事件代理
2015/11/06 Javascript
JavaScript预解析及相关技巧分析
2016/04/21 Javascript
BootstrapTable与KnockoutJS相结合实现增删改查功能【二】
2016/05/10 Javascript
前端js文件合并的三种方式推荐
2016/05/19 Javascript
如何使用angularJs
2017/05/08 Javascript
vue.js源代码core scedule.js学习笔记
2017/07/03 Javascript
vue二级路由设置方法
2018/02/09 Javascript
Vue 仿QQ左滑删除组件功能
2018/03/12 Javascript
JavaScript判断数组类型的方法
2019/10/23 Javascript
nodejs nedb 封装库与使用方法示例
2020/02/06 NodeJs
Python计算两个日期相差天数的方法示例
2017/05/23 Python
python删除不需要的python文件方法
2018/04/24 Python
python numpy实现文件存取的示例代码
2019/05/26 Python
python实现对图片进行旋转,放缩,裁剪的功能
2019/08/07 Python
详解用python计算阶乘的几种方法
2019/08/14 Python
Django怎么在admin后台注册数据库表
2020/11/14 Python
html5 移动端视频video的android兼容(去除播放控件、全屏)
2020/03/26 HTML / CSS
时尚设计师手表:The Watch Cabin
2018/10/06 全球购物
Tommy Hilfiger美国官网:美国高端休闲领导品牌
2019/01/14 全球购物
先进事迹报告会感言
2014/01/24 职场文书
简历中自我评价怎么写
2014/02/12 职场文书
新学期新寄语,献给新生们!
2019/11/15 职场文书
导游词之永泰公主墓
2019/12/04 职场文书
Tensorflow与RNN、双向LSTM等的踩坑记录及解决
2021/05/31 Python