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中的正则表达式(re模块)
Oct 17 Python
Python实现扣除个人税后的工资计算器示例
Mar 26 Python
python中pip的使用和修改下载源的方法
Jul 08 Python
Python 控制终端输出文字的实例
Jul 12 Python
django项目登录中使用图片验证码的实现方法
Aug 15 Python
结合OpenCV与TensorFlow进行人脸识别的实现
Oct 10 Python
使用python快速实现不同机器间文件夹共享方式
Dec 22 Python
Python面向对象封装操作案例详解
Dec 31 Python
Python随机数函数代码实例解析
Feb 09 Python
浅谈Python 命令行参数argparse写入图片路径操作
Jul 12 Python
python map比for循环快在哪
Sep 21 Python
python 实现mysql自动增删分区的方法
Apr 01 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实现将GB编码转换为UTF8
2006/11/25 PHP
基于php无限分类的深入理解
2013/06/02 PHP
基于命令行执行带参数的php脚本并取得参数的方法
2016/01/25 PHP
JavaScript实现表格排序方法
2013/06/14 Javascript
js调用打印机打印网页字体总是缩小一号的解决方法
2014/01/24 Javascript
JS替换字符串中字符即替换全部而不是第一个
2014/06/04 Javascript
JavaScript获取鼠标移动时的坐标(兼容IE8、chome谷歌、Firefox)
2014/09/13 Javascript
alert出数组中的随即值代码
2014/09/25 Javascript
完美解决jQuery的hover事件在IE中不停闪动的问题
2017/02/10 Javascript
JavaScript变量作用域_动力节点Java学院整理
2017/06/27 Javascript
jQuery访问浏览器本地存储cookie、localStorage和sessionStorage的基本用法
2017/10/20 jQuery
vue实现底部菜单功能
2018/07/24 Javascript
跟混乱的页面弹窗说再见
2019/04/11 Javascript
JS解惑之Object中的key是有序的么
2019/05/06 Javascript
微信小程序批量监听输入框对按钮样式进行控制的实现代码
2019/10/12 Javascript
vue-cli+iview项目打包上线之后图标不显示问题及解决方法
2019/10/16 Javascript
vue中解决拖拽改变存在iframe的div大小时卡顿问题
2020/07/22 Javascript
element-ui点击查看大图的方法示例
2020/12/14 Javascript
[56:17]NB vs Infamous 2019国际邀请赛淘汰赛 败者组 BO3 第三场 8.22
2019/09/05 DOTA
解决python3捕获cx_oracle抛出的异常错误问题
2018/10/18 Python
Python 限制线程的最大数量的方法(Semaphore)
2019/02/22 Python
Python使用sklearn实现的各种回归算法示例
2019/07/04 Python
Python爬虫爬取Bilibili弹幕过程解析
2019/10/10 Python
Python笔记之工厂模式
2019/11/20 Python
Python爬取爱奇艺电影信息代码实例
2019/11/26 Python
pyinstaller 3.6版本通过pip安装失败的解决办法(推荐)
2020/01/18 Python
python3操作注册表的方法(Url protocol)
2020/02/05 Python
Keras 加载已经训练好的模型进行预测操作
2020/06/17 Python
谈谈python垃圾回收机制
2020/09/27 Python
python 基于opencv实现高斯平滑
2020/12/18 Python
详解如何用canvas画一个微笑的表情
2019/03/14 HTML / CSS
Nike法国官方网站:Nike.com FR
2018/07/22 全球购物
Javascript如何发送一个Ajax请求
2015/01/26 面试题
饲料采购员岗位职责
2013/12/19 职场文书
欢迎领导标语
2014/06/27 职场文书
2015年商场工作总结
2015/04/27 职场文书