Skip to content

Commit 97e42c0

Browse files
committed
14.2小节完成
1 parent 9bdf5f9 commit 97e42c0

File tree

1 file changed

+162
-150
lines changed

1 file changed

+162
-150
lines changed

source/c14/p02_patching_objects_in_unit_tests.rst

Lines changed: 162 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -5,173 +5,185 @@
55
----------
66
问题
77
----------
8-
You’re writing unit tests and need to apply patches to selected objects in order to make
9-
assertions about how they were used in the test (e.g., assertions about being called with
10-
certain parameters, access to selected attributes, etc.).
8+
你写的单元测试中需要给指定的对象打补丁,
9+
用来断言它们在测试中的期望行为(比如,断言被调用时的参数个数,访问指定的属性等)。
1110

1211
|
1312
1413
----------
1514
解决方案
1615
----------
17-
The unittest.mock.patch() function can be used to help with this problem. It’s a little
18-
unusual, but patch() can be used as a decorator, a context manager, or stand-alone. For
19-
example, here’s an example of how it’s used as a decorator:
16+
``unittest.mock.patch()`` 函数可被用来解决这个问题。
17+
``patch()`` 还可被用作一个装饰器、上下文管理器或单独使用,尽管并不常见。
18+
例如,下面是一个将它当做装饰器使用的例子:
2019

21-
from unittest.mock import patch
22-
import example
20+
.. code-block:: python
2321
24-
@patch('example.func')
25-
def test1(x, mock_func):
26-
example.func(x) # Uses patched example.func
27-
mock_func.assert_called_with(x)
28-
It can also be used as a context manager:
22+
from unittest.mock import patch
23+
import example
2924
30-
with patch('example.func') as mock_func:
31-
example.func(x) # Uses patched example.func
32-
mock_func.assert_called_with(x)
25+
@patch('example.func')
26+
def test1(x, mock_func):
27+
example.func(x) # Uses patched example.func
28+
mock_func.assert_called_with(x)
3329
34-
Last, but not least, you can use it to patch things manually:
30+
它还可以被当做一个上下文管理器:
3531

36-
p = patch('example.func')
37-
mock_func = p.start()
38-
example.func(x)
39-
mock_func.assert_called_with(x)
40-
p.stop()
32+
.. code-block:: python
4133
42-
If necessary, you can stack decorators and context managers to patch multiple objects.
43-
For example:
34+
with patch('example.func') as mock_func:
35+
example.func(x) # Uses patched example.func
36+
mock_func.assert_called_with(x)
4437
45-
@patch('example.func1')
46-
@patch('example.func2')
47-
@patch('example.func3')
48-
def test1(mock1, mock2, mock3):
49-
...
38+
最后,你还可以手动的使用它打补丁:
5039

51-
def test2():
52-
with patch('example.patch1') as mock1, \
53-
patch('example.patch2') as mock2, \
54-
patch('example.patch3') as mock3:
55-
...
40+
.. code-block:: python
41+
42+
p = patch('example.func')
43+
mock_func = p.start()
44+
example.func(x)
45+
mock_func.assert_called_with(x)
46+
p.stop()
47+
48+
如果可能的话,你能够叠加装饰器和上下文管理器来给多个对象打补丁。例如:
49+
50+
.. code-block:: python
51+
52+
@patch('example.func1')
53+
@patch('example.func2')
54+
@patch('example.func3')
55+
def test1(mock1, mock2, mock3):
56+
...
57+
58+
def test2():
59+
with patch('example.patch1') as mock1, \
60+
patch('example.patch2') as mock2, \
61+
patch('example.patch3') as mock3:
62+
...
5663
5764
|
5865
5966
----------
6067
讨论
6168
----------
62-
patch() works by taking an existing object with the fully qualified name that you pro‐
63-
vide and replacing it with a new value. The original value is then restored after the
64-
completion of the decorated function or context manager. By default, values are replaced
65-
with MagicMock instances. For example:
66-
67-
>>> x = 42
68-
>>> with patch('__main__.x'):
69-
... print(x)
70-
...
71-
<MagicMock name='x' id='4314230032'>
72-
>>> x
73-
42
74-
>>>
75-
76-
However, you can actually replace the value with anything that you wish by supplying
77-
it as a second argument to patch():
78-
79-
>>> x
80-
42
81-
>>> with patch('__main__.x', 'patched_value'):
82-
... print(x)
83-
...
84-
patched_value
85-
>>> x
86-
42
87-
>>>
88-
89-
The MagicMock instances that are normally used as replacement values are meant to
90-
mimic callables and instances. They record information about usage and allow you to
91-
make assertions. For example:
92-
93-
>>> from unittest.mock import MagicMock
94-
>>> m = MagicMock(return_value = 10)
95-
>>> m(1, 2, debug=True)
96-
10
97-
>>> m.assert_called_with(1, 2, debug=True)
98-
>>> m.assert_called_with(1, 2)
99-
Traceback (most recent call last):
100-
File "<stdin>", line 1, in <module>
101-
File ".../unittest/mock.py", line 726, in assert_called_with
102-
raise AssertionError(msg)
103-
AssertionError: Expected call: mock(1, 2)
104-
Actual call: mock(1, 2, debug=True)
105-
>>>
106-
107-
>>> m.upper.return_value = 'HELLO'
108-
>>> m.upper('hello')
109-
'HELLO'
110-
>>> assert m.upper.called
111-
112-
>>> m.split.return_value = ['hello', 'world']
113-
>>> m.split('hello world')
114-
['hello', 'world']
115-
>>> m.split.assert_called_with('hello world')
116-
>>>
117-
118-
>>> m['blah']
119-
<MagicMock name='mock.__getitem__()' id='4314412048'>
120-
>>> m.__getitem__.called
121-
True
122-
>>> m.__getitem__.assert_called_with('blah')
123-
>>>
124-
125-
Typically, these kinds of operations are carried out in a unit test. For example, suppose
126-
you have some function like this:
127-
128-
# example.py
129-
from urllib.request import urlopen
130-
import csv
131-
132-
def dowprices():
133-
u = urlopen('http://finance.yahoo.com/d/quotes.csv?s=@^DJI&f=sl1')
134-
lines = (line.decode('utf-8') for line in u)
135-
rows = (row for row in csv.reader(lines) if len(row) == 2)
136-
prices = { name:float(price) for name, price in rows }
137-
return prices
138-
139-
Normally, this function uses urlopen() to go fetch data off the Web and parse it. To
140-
unit test it, you might want to give it a more predictable dataset of your own creation,
141-
however. Here’s an example using patching:
142-
143-
import unittest
144-
from unittest.mock import patch
145-
import io
146-
import example
147-
148-
sample_data = io.BytesIO(b'''\
149-
"IBM",91.1\r
150-
"AA",13.25\r
151-
"MSFT",27.72\r
152-
\r
153-
''')
154-
155-
class Tests(unittest.TestCase):
156-
@patch('example.urlopen', return_value=sample_data)
157-
def test_dowprices(self, mock_urlopen):
158-
p = example.dowprices()
159-
self.assertTrue(mock_urlopen.called)
160-
self.assertEqual(p,
161-
{'IBM': 91.1,
162-
'AA': 13.25,
163-
'MSFT' : 27.72})
164-
165-
if __name__ == '__main__':
166-
unittest.main()
167-
168-
In this example, the urlopen() function in the example module is replaced with a mock
169-
object that returns a BytesIO() containing sample data as a substitute.
170-
An important but subtle facet of this test is the patching of example.urlopen instead of
171-
urllib.request.urlopen. When you are making patches, you have to use the names
172-
as they are used in the code being tested. Since the example code uses from urllib.re
173-
quest import urlopen, the urlopen() function used by the dowprices() function is
174-
actually located in example.
175-
This recipe has really only given a very small taste of what’s possible with the uni
176-
ttest.mock module. The official documentation is a must-read for more advanced
177-
features.
69+
``patch()`` 接受一个已存在对象的全路径名,将其替换为一个新的值。
70+
原来的值会在装饰器函数或上下文管理器完成后自动恢复回来。
71+
默认情况下,所有值会被 ``MagicMock`` 实例替代。例如:
72+
73+
.. code-block:: python
74+
75+
>>> x = 42
76+
>>> with patch('__main__.x'):
77+
... print(x)
78+
...
79+
<MagicMock name='x' id='4314230032'>
80+
>>> x
81+
42
82+
>>>
83+
84+
不过,你可以通过给 ``patch()`` 提供第二个参数来将值替换成任何你想要的:
85+
86+
.. code-block:: python
87+
88+
>>> x
89+
42
90+
>>> with patch('__main__.x', 'patched_value'):
91+
... print(x)
92+
...
93+
patched_value
94+
>>> x
95+
42
96+
>>>
97+
98+
被用来作为替换值的 ``MagicMock`` 实例能够模拟可调用对象和实例。
99+
他们记录对象的使用信息并允许你执行断言检查,例如:
100+
101+
.. code-block:: python
102+
103+
>>> from unittest.mock import MagicMock
104+
>>> m = MagicMock(return_value = 10)
105+
>>> m(1, 2, debug=True)
106+
10
107+
>>> m.assert_called_with(1, 2, debug=True)
108+
>>> m.assert_called_with(1, 2)
109+
Traceback (most recent call last):
110+
File "<stdin>", line 1, in <module>
111+
File ".../unittest/mock.py", line 726, in assert_called_with
112+
raise AssertionError(msg)
113+
AssertionError: Expected call: mock(1, 2)
114+
Actual call: mock(1, 2, debug=True)
115+
>>>
116+
117+
>>> m.upper.return_value = 'HELLO'
118+
>>> m.upper('hello')
119+
'HELLO'
120+
>>> assert m.upper.called
121+
122+
>>> m.split.return_value = ['hello', 'world']
123+
>>> m.split('hello world')
124+
['hello', 'world']
125+
>>> m.split.assert_called_with('hello world')
126+
>>>
127+
128+
>>> m['blah']
129+
<MagicMock name='mock.__getitem__()' id='4314412048'>
130+
>>> m.__getitem__.called
131+
True
132+
>>> m.__getitem__.assert_called_with('blah')
133+
>>>
134+
135+
一般来讲,这些操作会在一个单元测试中完成。例如,假设你已经有了像下面这样的函数:
136+
137+
.. code-block:: python
138+
139+
# example.py
140+
from urllib.request import urlopen
141+
import csv
142+
143+
def dowprices():
144+
u = urlopen('http://finance.yahoo.com/d/quotes.csv?s=@^DJI&f=sl1')
145+
lines = (line.decode('utf-8') for line in u)
146+
rows = (row for row in csv.reader(lines) if len(row) == 2)
147+
prices = { name:float(price) for name, price in rows }
148+
return prices
149+
150+
正常来讲,这个函数会使用 ``urlopen()`` 从Web上面获取数据并解析它。
151+
在单元测试中,你可以给它一个预先定义好的数据集。下面是使用补丁操作的例子:
152+
153+
.. code-block:: python
154+
155+
import unittest
156+
from unittest.mock import patch
157+
import io
158+
import example
159+
160+
sample_data = io.BytesIO(b'''\
161+
"IBM",91.1\r
162+
"AA",13.25\r
163+
"MSFT",27.72\r
164+
\r
165+
''')
166+
167+
class Tests(unittest.TestCase):
168+
@patch('example.urlopen', return_value=sample_data)
169+
def test_dowprices(self, mock_urlopen):
170+
p = example.dowprices()
171+
self.assertTrue(mock_urlopen.called)
172+
self.assertEqual(p,
173+
{'IBM': 91.1,
174+
'AA': 13.25,
175+
'MSFT' : 27.72})
176+
177+
if __name__ == '__main__':
178+
unittest.main()
179+
180+
本例中,位于 ``example`` 模块中的 ``urlopen()`` 函数被一个模拟对象替代,
181+
该对象会返回一个包含测试数据的 ``ByteIO()``.
182+
183+
还有一点,在打补丁时我们使用了 ``example.urlopen`` 来代替 ``urllib.request.urlopen`` 。
184+
当你创建补丁的时候,你必须使用它们在测试代码中的名称。
185+
由于测试代码使用了 ``from urllib.request import urlopen`` ,那么 ``dowprices()`` 函数
186+
中使用的 ``urlopen()`` 函数实际上就位于 ``example`` 模块了。
187+
188+
本节实际上只是对 ``unittest.mock`` 模块的一次浅尝辄止。
189+
更多更高级的特性,请参考 `官方文档 <http://docs.python.org/3/library/unittest.mock>`_

0 commit comments

Comments
 (0)