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简单实现查找一个字符串中最长不重复子串的方法
Mar 26 Python
python 读取txt,json和hdf5文件的实例
Jun 05 Python
Python通过for循环理解迭代器和生成器实例详解
Feb 16 Python
基于Python实现用户管理系统
Feb 26 Python
Python面向对象思想与应用入门教程【类与对象】
Apr 12 Python
Django项目创建到启动详解(最全最详细)
Sep 07 Python
利用python计算时间差(返回天数)
Sep 07 Python
python 如何将数据写入本地txt文本文件的实现方法
Sep 11 Python
python动态视频下载器的实现方法
Sep 16 Python
python 3.7.4 安装 opencv的教程
Oct 10 Python
Python实现图像去噪方式(中值去噪和均值去噪)
Dec 18 Python
python使用自定义钉钉机器人的示例代码
Jun 24 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快速url重写更新版[需php 5.30以上]
2010/04/25 PHP
php 记录进行累加并显示总时长为秒的结果
2011/11/04 PHP
PHP连接SQLServer2005的实现方法(附ntwdblib.dll下载)
2012/07/02 PHP
表格展示无限级分类(PHP版)
2012/08/21 PHP
PHP笔记之:日期函数的使用介绍
2013/04/24 PHP
Yii2实现多域名跨域同步登录退出
2017/02/04 PHP
thinkPHP3.2.2框架行为扩展及demo示例
2018/06/19 PHP
完整显示当前日期和时间的JS代码
2007/09/17 Javascript
Jquery Ajax解析XML数据(同步及异步调用)简单实例
2014/02/12 Javascript
jQuery中empty()方法用法实例
2015/01/16 Javascript
介绍JavaScript的一个微型模版
2015/06/24 Javascript
js实现的万能flv网页播放器代码
2016/04/30 Javascript
JavaScript必知必会(三) String .的方法来自何方
2016/06/08 Javascript
浅谈angularJS中的事件
2016/07/12 Javascript
给easyui datebox扩展一个清空的实例
2016/11/09 Javascript
jquery网页日历显示控件calendar3.1使用详解
2016/11/24 Javascript
JavaScript 详解预编译原理
2017/01/22 Javascript
angular-ngSanitize模块-$sanitize服务详解
2017/06/13 Javascript
React 子组件向父组件传值的方法
2017/07/24 Javascript
AngualrJs清除定时器遇到的坑
2017/10/13 Javascript
微信小程序日历/日期选择插件使用方法详解
2018/12/28 Javascript
JavaScript 斐波那契数列 倒序输出 输出100以内的质数代码实例
2019/09/11 Javascript
解决vant中 tab栏遇到的坑 van-tabs
2020/11/04 Javascript
[02:37]2018DOTA2亚洲邀请赛赛前采访-EG篇
2018/04/03 DOTA
python用ConfigObj读写配置文件的实现代码
2013/03/04 Python
python继承和抽象类的实现方法
2015/01/14 Python
PyQt5每天必学之单行文本框
2018/04/19 Python
解决Python print输出不换行没空格的问题
2018/11/14 Python
python 使用cycle构造无限循环迭代器
2020/12/02 Python
Boden美国官网:英伦原创时装品牌
2017/07/03 全球购物
加拿大鞋网:Globo Shoes
2019/12/26 全球购物
大学生求职信范文应怎么写
2014/01/01 职场文书
天地会口号
2014/06/17 职场文书
2014年度思想工作总结
2014/11/27 职场文书
详解CSS玩转图片Base64编码
2021/05/25 HTML / CSS
插件导致ECharts被全量引入的坑示例解析
2022/09/23 Javascript