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 相关文章推荐
使用IPython来操作Docker容器的入门指引
Apr 08 Python
Python单元测试框架unittest简明使用实例
Apr 13 Python
Python实现简单字典树的方法
Apr 29 Python
python正则表达式的使用
Jun 12 Python
python实现媒体播放器功能
Feb 11 Python
Python 修改列表中的元素方法
Jun 26 Python
python 字典修改键(key)的几种方法
Aug 10 Python
python下的opencv画矩形和文字注释的实现方法
Jul 09 Python
python代码编写计算器小程序
Mar 30 Python
Django中的用户身份验证示例详解
Aug 07 Python
利用python实现平稳时间序列的建模方式
Jun 03 Python
Keras:Unet网络实现多类语义分割方式
Jun 11 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防范SQL注入的具体方法详解(测试通过)
2014/05/09 PHP
采用thinkphp自带方法生成静态html文件详解
2014/06/13 PHP
Windows下安装PHP单元测试环境PHPUnit图文教程
2014/10/24 PHP
Linux系统下php获得系统分区信息的方法
2015/03/30 PHP
基于CakePHP实现的简单博客系统实例
2015/06/28 PHP
PHP之将POST数据转化为字符串的实现代码
2016/11/03 PHP
javascript编程起步(第七课)
2007/01/10 Javascript
IE浏览器PNG图片透明效果代码
2008/09/02 Javascript
JavaScript中URL编码函数代码
2011/01/11 Javascript
jquery插件开发方法(初学者)
2012/02/03 Javascript
js里取容器大小、定位、距离等属性搜集整理
2013/08/19 Javascript
浅析javascript中的DOM
2015/03/01 Javascript
JS动态计算移动端rem的解决方案
2016/10/14 Javascript
JS实现简单表格排序操作示例
2017/10/07 Javascript
node.js支持多用户web终端实现及安全方案
2017/11/29 Javascript
微信小程序实现bindtap等事件传参
2019/04/08 Javascript
Vue事件修饰符native、self示例详解
2019/07/09 Javascript
vue页面更新patch的实现示例
2020/03/25 Javascript
解决vue项目中遇到 Cannot find module ‘chalk‘ 报错的问题
2020/11/05 Javascript
解决谷歌搜索技术文章时打不开网页问题的python脚本
2013/02/10 Python
Python基于正则表达式实现计算器功能
2020/07/13 Python
python 爬取免费简历模板网站的示例
2020/09/27 Python
HTML5 Video标签的属性、方法和事件汇总介绍
2015/04/24 HTML / CSS
HTML5录音实践总结(Preact)
2020/05/07 HTML / CSS
html5 拖拽及用 js 实现拖拽功能的示例代码
2020/10/23 HTML / CSS
John Varvatos官方网站:设计师男士时装
2017/02/08 全球购物
FC-Moto丹麦:欧洲最大的摩托车服装和头盔商店之一
2019/08/20 全球购物
出纳的岗位职责
2013/11/09 职场文书
开学典礼主持词
2014/03/19 职场文书
校园活动宣传方案
2014/03/28 职场文书
《最佳路径》教学反思
2014/04/13 职场文书
公司员工辞职信范文
2015/05/12 职场文书
聘任书的格式及模板
2019/10/28 职场文书
SqlServer 垂直分表(减少程序改动)
2021/04/16 SQL Server
MySQL Innodb索引机制详细介绍
2021/11/23 MySQL
Docker 镜像介绍以及commit相关操作
2022/04/13 Servers