使用 patch() 来模拟我没有明确导入的东西
Using patch() to mock something that I don't explicilty import
假设我有以下模块:
# src/myapp/utils.py
import thirdparty
from myapp.secrets import get_super_secret_stuff
def get_thirdparty_client() -> return thirdparty.Client:
thirdparty.Client(**get_super_secret_stuff())
# src/myapp/things.py
from myapp.utils import get_thirdparty_client
from myapp.transformations import apply_fairydust, make_it_sparkle
def do_something(x):
thirdparty_client = get_thirdparty_client()
y = thidparty_client.query(apply_fairydust(x))
return make_it_sparkle(y)
假设 myapp
是经过轻微测试的遗留代码,并且重构是不可能的。还假设(烦人地)thirdparty.Client
在其 __init__
方法中执行非确定性网络 I/O。因此,我打算模拟 thirdparty.Client
class 本身,以使这个 do_something
函数可测试。
还假设我必须使用 unittest
并且不能使用其他测试框架,如 Pytest。
unittest.mock
中的 patch
函数似乎是完成这项工作的正确工具。但是,我不确定如何将通常的警告应用到“使用补丁的地方”。
理想情况下,我想编写一个如下所示的测试:
# tests/test_myapp/test_things.py
from unittest import TestCase
from unittest.mock import patch
from myapp.things import do_something
def gen_test_pairs():
# Generate pairs of expected inputs and outputs
...
class ThingTest(unittest.TestCase):
@patch('????')
def test_do_something(self, mock_thirdparty_client):
for x, y in gen_test_pairs:
with self.subTest(params={'x': x, 'y': y}):
mock_thirdparty_client.query.return_value = y
self.assertEqual(do_something(x), y)
我的问题是我不知道用什么来代替 ????
,因为我从来没有真正在 src/myapp/things.py
中导入 thirdparty.Client
。
我考虑的选项:
- 在
myapp.utils.thirdparty.Client
应用补丁,这使我的测试变得脆弱并依赖于实现细节。
- “打破规则”并在
thirdparty.Client
应用补丁。
- 在测试中导入
get_thirdparty_client
,在上面使用patch.object
,并将其return_value
设置为我单独创建的另一个MagicMock
,这第二个mock就可以了参加 thirdparty.Client
。这会导致更冗长的测试代码无法作为单个装饰器轻松应用。
None 这些选项听起来特别吸引人,但我不知道哪个被认为是最不糟糕的。
或者是否有其他我没有看到的选项可供选择?
正确答案是 2:将补丁应用于 thirdparty.Client
。
这是正确的,因为它实际上并没有违反“使用补丁的地方”规则。
该规则是描述性的,而不是字面意思。在这种情况下,thirdparty.Client
被认为在 thirdparty
模块中被“使用”。
Lisa Roach 在 2018 年 PyCon 演讲“揭秘补丁函数”中更详细地描述了这个概念。 YouTube 上提供了完整的录音:https://youtu.be/ww1UsGZV8fQ?t=537。这个特殊案例的解释从视频开始大约 9 分钟开始。
假设我有以下模块:
# src/myapp/utils.py
import thirdparty
from myapp.secrets import get_super_secret_stuff
def get_thirdparty_client() -> return thirdparty.Client:
thirdparty.Client(**get_super_secret_stuff())
# src/myapp/things.py
from myapp.utils import get_thirdparty_client
from myapp.transformations import apply_fairydust, make_it_sparkle
def do_something(x):
thirdparty_client = get_thirdparty_client()
y = thidparty_client.query(apply_fairydust(x))
return make_it_sparkle(y)
假设 myapp
是经过轻微测试的遗留代码,并且重构是不可能的。还假设(烦人地)thirdparty.Client
在其 __init__
方法中执行非确定性网络 I/O。因此,我打算模拟 thirdparty.Client
class 本身,以使这个 do_something
函数可测试。
还假设我必须使用 unittest
并且不能使用其他测试框架,如 Pytest。
unittest.mock
中的 patch
函数似乎是完成这项工作的正确工具。但是,我不确定如何将通常的警告应用到“使用补丁的地方”。
理想情况下,我想编写一个如下所示的测试:
# tests/test_myapp/test_things.py
from unittest import TestCase
from unittest.mock import patch
from myapp.things import do_something
def gen_test_pairs():
# Generate pairs of expected inputs and outputs
...
class ThingTest(unittest.TestCase):
@patch('????')
def test_do_something(self, mock_thirdparty_client):
for x, y in gen_test_pairs:
with self.subTest(params={'x': x, 'y': y}):
mock_thirdparty_client.query.return_value = y
self.assertEqual(do_something(x), y)
我的问题是我不知道用什么来代替 ????
,因为我从来没有真正在 src/myapp/things.py
中导入 thirdparty.Client
。
我考虑的选项:
- 在
myapp.utils.thirdparty.Client
应用补丁,这使我的测试变得脆弱并依赖于实现细节。 - “打破规则”并在
thirdparty.Client
应用补丁。 - 在测试中导入
get_thirdparty_client
,在上面使用patch.object
,并将其return_value
设置为我单独创建的另一个MagicMock
,这第二个mock就可以了参加thirdparty.Client
。这会导致更冗长的测试代码无法作为单个装饰器轻松应用。
None 这些选项听起来特别吸引人,但我不知道哪个被认为是最不糟糕的。
或者是否有其他我没有看到的选项可供选择?
正确答案是 2:将补丁应用于 thirdparty.Client
。
这是正确的,因为它实际上并没有违反“使用补丁的地方”规则。
该规则是描述性的,而不是字面意思。在这种情况下,thirdparty.Client
被认为在 thirdparty
模块中被“使用”。
Lisa Roach 在 2018 年 PyCon 演讲“揭秘补丁函数”中更详细地描述了这个概念。 YouTube 上提供了完整的录音:https://youtu.be/ww1UsGZV8fQ?t=537。这个特殊案例的解释从视频开始大约 9 分钟开始。