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爬虫之urllib2中的两个重要概念:Openers和Handlers
Nov 05 Python
python使用urllib2实现发送带cookie的请求
Apr 28 Python
Python3实现从文件中读取指定行的方法
May 22 Python
Python中的os.path路径模块中的操作方法总结
Jul 07 Python
使用python实现个性化词云的方法
Jun 16 Python
python中模块查找的原理与方法详解
Aug 11 Python
itchat和matplotlib的结合使用爬取微信信息的实例
Aug 25 Python
Python实现图片尺寸缩放脚本
Mar 10 Python
python实现学生信息管理系统
Apr 05 Python
Python numpy中矩阵的基本用法汇总
Feb 12 Python
appium+python自动化配置(adk、jdk、node.js)
Nov 17 Python
Python系统公网私网流量监控实现流程
Nov 23 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 加密/解密函数 dencrypt(动态密文,带压缩功能,支持中文)
2009/01/30 PHP
TP框架实现上传一张图片和批量上传图片的方法分析
2020/04/23 PHP
javascript网页关键字高亮代码
2008/07/30 Javascript
jQuery操作Select的Option上下移动及移除添加等等
2013/11/18 Javascript
5个书写JavaScript代码的坏习惯,看看你中枪了没?
2014/11/06 Javascript
jquery获取checkbox的值并post提交
2015/01/14 Javascript
JavaScript实现的SHA-1加密算法完整实例
2016/02/02 Javascript
jQuery版AJAX简易封装代码
2016/09/14 Javascript
移动适配的几种方案(三种方案)
2016/11/25 Javascript
用Vue写一个分页器的示例代码
2018/04/22 Javascript
js console.log打印对象时属性缺失的解决方法
2019/05/23 Javascript
js实现窗口全屏示例详解
2019/09/17 Javascript
selenium+java中用js来完成日期的修改
2019/10/31 Javascript
JavaScript 实现HTML DOM增删改查操作的常见方法详解
2020/01/04 Javascript
mpvue实现微信小程序快递单号查询代码
2020/04/03 Javascript
python字符串连接的N种方式总结
2014/09/17 Python
python 把数据 json格式输出的实例代码
2016/10/31 Python
python利用高阶函数实现剪枝函数
2018/03/20 Python
Python格式化输出字符串方法小结【%与format】
2018/10/29 Python
使用Python实现微信提醒备忘录功能
2018/12/04 Python
Django框架首页和登录页分离操作示例
2019/05/28 Python
关于tf.nn.dynamic_rnn返回值详解
2020/01/20 Python
解决Python在导入文件时的FileNotFoundError问题
2020/04/10 Python
python实现图书馆抢座(自动预约)功能的示例代码
2020/09/29 Python
联想西班牙官网:Lenovo西班牙
2018/08/28 全球购物
优秀干部获奖感言
2014/01/31 职场文书
安全生产月标语
2014/10/07 职场文书
外贸业务员岗位职责
2015/02/13 职场文书
堂吉诃德读书笔记
2015/06/30 职场文书
小学秋季运动会通讯稿
2015/11/25 职场文书
趣味运动会口号
2015/12/24 职场文书
公司会议开幕词
2016/03/03 职场文书
庭外和解协议书
2016/03/23 职场文书
OpenCV-Python实现怀旧滤镜与连环画滤镜
2021/06/09 Python
Python各协议下socket黏包问题原理
2022/04/12 Python
vue 给数组添加新对象并赋值
2022/04/20 Vue.js