从同一个包中修补函数不是修补

Patching a function from within the same package is not patching

我正在使用内置的单元测试库。我正在尝试测试一个函数,在此示例中 a,它调用函数 b。这两个函数都在包的顶层,不是基于 class 的函数。我已经阅读了关于在哪里打补丁的 unittest 文档,发现你必须使用补丁来对抗函数(a)导入另一个函数(b)而不是修补函数本身(b),但所有的答案和文档似乎只引用了基于 class 的函数。

我有以下层次结构:

mypkg
|- __init__.py
|- a.py
|- b.py
|- test
  |- __init__.py
  |- test_a.py

顶级__init__.py:

from .a import a
from .b import b
__all__ = ['a','b']

a.py:

import .b import b

def a():
  ...
  functionB({"USERNAME": username, "OTHER": other})

b.py:

def b(**kwargs):
 // do stuff with kwargs like calling APIs etc
 // This function doesn't return anything

test_a.py:

import unittest
from unittest.mock import patch
from ..a import a

fake_return_value = "heythere"

class TestFunctionA(unittest.TestCase):
 @patch('mypkg.a.b', return_value=fake_return_value)
 # @patch('mympkg.b') # Doesn't work, does not override the original function
 def test_mytest(self, mocked_func):
   a()
   mocked_func.assert_called_once_with(stuff)

运行 上述带有未注释的补丁调用的测试导致测试 运行 并且调用的是原始函数,而不是模拟函数。

我在这里做错了什么?我不认为单元测试会这么难,我来自单元测试 React 应用程序,所以这有点令人沮丧,但我认为用户错误。到目前为止,我所做的所有文档和谷歌搜索似乎都应该有效,或者至少是上面注释掉的补丁变体。这是因为我的包结构并试图在同一个包中模拟函数吗?

注意:此答案解决了 previous revision 问题

注释掉的 patch 确实是正确的使用方法,因为您正在尝试修补 ab 的引用。 b 是一个 class 还是一个函数并不重要,因为像 JavaScript 一样,它们都是 Python 中的 first-class 对象并且被查找和unittest.mock.patch 以同样的方式修补。

我怀疑您的更复杂系统中的问题与示例中的以下错误之一有关:

  • patch 应导入为 from unittest.mock import patch.
  • 模拟的 return_value 应该在您的测试中分配,而不是在装饰器中分配。
  • stuff 不在您的测试范围内 class.

有效的等效示例

这是您描述的系统的一个工作示例:

  • 两个模块,ab,每个模块都有一个 top-level 函数
  • 模块 ab 调用函数。
  • 您想修补 b 中的函数以单独测试模块 a

文件层次结构:

mypkg
|- __init__.py    // empty
|- a.py
|- b.py
|- test
  |- __init__.py  // empty
  |- test_a.py

b.py

def b():
  return "World!"

a.py

from .b import b

def a():
  return "Hello " + b()

test_a.py

import unittest
from unittest.mock import patch
from ..a import a

class TestFunctionA(unittest.TestCase):
    def test_a_no_mock(self):
        message = a()
        self.assertEqual(message, "Hello World!")

    @patch('mypkg.a.b')
    def test_mytest(self, mocked_func):
        mocked_func.return_value = "Stack Overflow!"
        message = a()
        self.assertEqual(message, "Hello Stack Overflow!")
        mocked_func.assert_called_once()

运行 来自文件夹 的测试 mypkg

$ ls
mypkg
$ python -m unittest discover
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

让我们看看您的顶层 __init__.py,其中包含在 导入时间 时执行的代码。

语句 from .a import a 将名称 a 模块 a 重新绑定到 ]function a 包含在模块 a 中。因此,所有 import mypkg.a 的调用(例如测试模块中的补丁)将 return 函数 a 而不是 模块。这就是为什么您在对我上一个答案的评论中提到的错误 AttributeError: function a does not have the attribute 'b'

要修补模块 a 对函数 b 的使用,您的测试代码需要能够导入模块 a。有几种方法可以做到这一点。

  • 删除 __init__.py 中的 from .a import a
    • 您仍然可以通过 from mypkg import aimport mypkg.a
    • 访问 模块 a
    • 您仍然可以通过 from mypkg.a import aimport mypkg.a.a
    • 访问 功能 a
  • 函数 a 重命名为其他名称。