首页 > 解决方案 > 使用 patch() 来模拟我没有明确导入的东西

问题描述

假设我有以下模块:

# 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执行非确定性网络 I/O 。__init__因此,我打算模拟thirdparty.Client类本身,以使该do_something功能可测试。

还假设我必须使用unittest并且不能使用像 Pytest 这样的另一个测试框架。

看起来patch函数 fromunittest.mock是适合这项工作的工具。但是,我不确定如何将通常的警告应用于“使用补丁的地方”。

理想情况下,我想编写一个看起来像这样的测试:

# 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)

我的问题是我不知道该写什么来代替,????因为我从来没有真正导入thirdparty.Client.src/myapp/things.py

我考虑的选项:

  1. 在 处应用补丁myapp.utils.thirdparty.Client,这使我的测试变得脆弱并依赖于实现细节。
  2. “打破规则”并在thirdparty.Client.
  3. get_thirdparty_client在测试中导入,在其上使用patch.object,并将其设置return_value为我单独创建的另一个MagicMock,第二个模拟将代表thirdparty.Client. 这使得更冗长的测试代码不能轻易地用作单个装饰器。

这些选项听起来都不是特别吸引人,但我不知道哪个被认为是最不糟糕的。

或者是否有其他我没有看到的选项可供我使用?

标签: pythonpython-unittest

解决方案


正确答案是 2:将补丁应用于thirdparty.Client.

这是正确的,因为它实际上并没有破坏“使用它的补丁”规则。

该规则旨在是描述性的,而不是文字的。在这种情况下,thirdparty.Client被认为是在thirdparty模块中“使用”。

这个概念在 Lisa Roach 的 2018 PyCon 演讲“Demystifying the Patch Function”中有更详细的描述。完整的录音可在 YouTube 上找到:https ://youtu.be/ww1UsGZV8fQ?t=537 。这个特殊案例的解释从视频大约 9 分钟开始。


推荐阅读