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的web框架编写MVC配置来使其运行的教程
Apr 30 Python
浅谈Python的文件类型
May 30 Python
matplotlib在python上绘制3D散点图实例详解
Dec 09 Python
对python中的pop函数和append函数详解
May 04 Python
Anaconda2下实现Python2.7和Python3.5的共存方法
Jun 11 Python
Python函数装饰器实现方法详解
Dec 22 Python
Python实现字符串匹配的KMP算法
Apr 04 Python
使用python实现unix2dos和dos2unix命令的例子
Aug 13 Python
python创建学生成绩管理系统
Nov 22 Python
Python日期格式和字符串格式相互转换的方法
Feb 18 Python
编写python代码实现简单抽奖器
Oct 20 Python
Python安装使用Scrapy框架
Apr 12 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设计模式 FlyWeight (享元模式)
2011/06/26 PHP
解析使用substr截取UTF-8中文字符串出现乱码的问题
2013/06/20 PHP
解析php函数method_exists()与is_callable()的区别
2013/06/21 PHP
PHP的拦截器实例分析
2014/11/03 PHP
Laravel下生成验证码的类
2017/11/15 PHP
php操作redis数据库常见方法实例总结
2020/02/20 PHP
到处都是jQuery选择器的年代 不了解它们的性能,行吗
2012/06/18 Javascript
js 控制下拉菜单刷新的方法
2013/03/03 Javascript
JS Jquery 遍历,筛选页面元素 自动完成(实现代码)
2013/07/08 Javascript
javascript用户注册提示效果的简单实例
2013/08/17 Javascript
Checbox的操作含已选、未选及判断代码
2013/11/07 Javascript
在页面上用action传递参数到后台出现乱码的解决方法
2013/12/31 Javascript
jQuery中:has选择器用法实例
2014/12/30 Javascript
分享jQuery网页元素拖拽插件
2020/12/01 Javascript
jQuery中的on与bind绑定事件区别实例详解
2017/02/28 Javascript
关于vue中的ajax请求和axios包问题
2018/04/19 Javascript
react quill中图片上传由默认转成base64改成上传到服务器的方法
2019/10/30 Javascript
微信小程序canvas分享海报功能
2019/10/31 Javascript
vue实现div可拖动位置也可改变盒子大小的原理
2020/09/16 Javascript
[02:39]我与DAC之Newbee.Moogy:从论坛到TI
2018/03/26 DOTA
[58:23]LGD vs TNC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
[22:07]DOTA2-DPC中国联赛 正赛 iG vs Magma 选手采访
2021/03/11 DOTA
用Python编写一个每天都在系统下新建一个文件夹的脚本
2015/05/04 Python
Python中将dataframe转换为字典的实例
2018/04/13 Python
python中time库的实例使用方法
2019/10/31 Python
Python实现UDP程序通信过程图解
2020/05/15 Python
python math模块的基本使用教程
2021/01/16 Python
HTML5 本地存储之如果没有数据库究竟会怎样
2013/04/25 HTML / CSS
初入社会应届生求职信
2013/11/18 职场文书
战友聚会主持词
2014/04/02 职场文书
槐乡的孩子教学反思
2014/04/27 职场文书
心理健康活动总结
2014/04/30 职场文书
党的群众路线教育实践活动心得体会(医院)
2014/11/03 职场文书
党支部考察鉴定意见
2015/06/02 职场文书
使用nginx动态转换图片大小生成缩略图
2021/03/31 Servers
golang json数组拼接的实例
2021/04/28 Golang