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 04 Python
在Python中操作列表之list.extend()方法的使用
May 20 Python
python Flask实现restful api service
Dec 04 Python
python远程邮件控制电脑升级版
May 23 Python
对python中不同模块(函数、类、变量)的调用详解
Jul 16 Python
Python generator生成器和yield表达式详解
Aug 08 Python
python3 dict ndarray 存成json,并保留原数据精度的实例
Dec 06 Python
jupyter notebook实现显示行号
Apr 13 Python
python 图像插值 最近邻、双线性、双三次实例
Jul 05 Python
Python延迟绑定问题原理及解决方案
Aug 04 Python
Django数据模型中on_delete使用详解
Nov 30 Python
一个入门级python爬虫教程详解
Jan 27 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生成生成缩略图的两个选择和区别
2007/04/17 PHP
php array_map()数组函数使用说明
2011/07/12 PHP
php自动注册登录验证机制实现代码
2011/12/20 PHP
php表单敏感字符过滤类
2014/12/08 PHP
PHP递归调用数组值并用其执行指定函数的方法
2015/04/01 PHP
php实现微信扫码支付
2017/03/26 PHP
基于jquery实现的表格分页实现代码
2011/06/21 Javascript
一个检测表单数据的JavaScript实例
2014/10/31 Javascript
js实现双击图片放大单击缩小的方法
2015/02/17 Javascript
jQuery实现的产品自动360度旋转展示特效源码分享
2015/08/21 Javascript
JavaScript中数组Array方法详解
2017/02/27 Javascript
vue.js数据绑定的方法(单向、双向和一次性绑定)
2017/07/13 Javascript
jQuery除指定区域外点击任何地方隐藏DIV功能
2017/11/13 jQuery
Vue写一个简单的倒计时按钮功能
2018/04/20 Javascript
bootstrap动态调用select下拉框的实例代码
2018/08/09 Javascript
详解如何为你的angular app构建一个第三方库
2018/12/07 Javascript
Web安全之XSS攻击与防御小结
2018/12/13 Javascript
wx-charts 微信小程序图表插件的具体使用
2019/08/18 Javascript
解决echarts echarts数据动态更新和dataZoom被重置问题
2020/07/20 Javascript
[06:10]6.81新信使新套装!给你一个炫酷的DOTA2
2014/05/06 DOTA
Python mutiprocessing多线程池pool操作示例
2019/01/30 Python
Python 通过requests实现腾讯新闻抓取爬虫的方法
2019/02/22 Python
python实现126邮箱发送邮件
2020/05/20 Python
django实现日志按日期分割
2020/05/21 Python
自荐书格式
2013/12/01 职场文书
医大实习自我鉴定
2013/12/07 职场文书
教师试用期自我鉴定
2014/02/12 职场文书
我的梦想演讲稿
2014/04/30 职场文书
公司承诺书格式
2014/05/21 职场文书
12.4全国法制宣传日活动总结
2014/11/01 职场文书
2014年销售人员工作总结
2014/11/27 职场文书
《折线统计图》教学反思
2016/02/22 职场文书
2019餐饮行业创业计划书!
2019/06/27 职场文书
python 如何在 Matplotlib 中绘制垂直线
2021/04/02 Python
CSS3 实现NES游戏机的示例代码
2021/04/21 HTML / CSS
Nginx 常用配置
2022/05/15 Servers