从同一个包中修补函数不是修补
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
确实是正确的使用方法,因为您正在尝试修补 a
对 b
的引用。 b
是一个 class 还是一个函数并不重要,因为像 JavaScript 一样,它们都是 Python 中的 first-class 对象并且被查找和unittest.mock.patch
以同样的方式修补。
我怀疑您的更复杂系统中的问题与示例中的以下错误之一有关:
patch
应导入为 from unittest.mock import patch
.
- 模拟的
return_value
应该在您的测试中分配,而不是在装饰器中分配。
stuff
不在您的测试范围内 class.
有效的等效示例
这是您描述的系统的一个工作示例:
- 两个模块,
a
和 b
,每个模块都有一个 top-level 函数
- 模块
a
从 b
调用函数。
- 您想修补
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 a
或 import mypkg.a
访问 模块 a
- 您仍然可以通过
from mypkg.a import a
或 import mypkg.a.a
访问 功能 a
- 将 函数
a
重命名为其他名称。
我正在使用内置的单元测试库。我正在尝试测试一个函数,在此示例中 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
确实是正确的使用方法,因为您正在尝试修补 a
对 b
的引用。 b
是一个 class 还是一个函数并不重要,因为像 JavaScript 一样,它们都是 Python 中的 first-class 对象并且被查找和unittest.mock.patch
以同样的方式修补。
我怀疑您的更复杂系统中的问题与示例中的以下错误之一有关:
patch
应导入为from unittest.mock import patch
.- 模拟的
return_value
应该在您的测试中分配,而不是在装饰器中分配。 stuff
不在您的测试范围内 class.
有效的等效示例
这是您描述的系统的一个工作示例:
- 两个模块,
a
和b
,每个模块都有一个 top-level 函数 - 模块
a
从b
调用函数。 - 您想修补
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 a
或import mypkg.a
访问 模块 - 您仍然可以通过
from mypkg.a import a
或import mypkg.a.a
访问 功能
a
a
- 您仍然可以通过
- 将 函数
a
重命名为其他名称。