14.2 在å�•元测试ä¸ç»™å¯¹è±¡æ‰“è¡¥ä¸�¶
问题¶
ä½ å†™çš„å�•元测试ä¸éœ€è¦�给指定的对象打补ä¸�, 用æ�¥æ–言它们在测试ä¸çš„æœŸæœ›è¡Œä¸ºï¼ˆæ¯”如,æ–言被调用时的å�‚数个数,访问指定的属性ç‰ï¼‰ã€‚
解决方案¶
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 模å�—的一次浅å°�辄æ¢ã€‚
更多更高级的特性,请�考 官方文档