如何恢复模拟
How to revert Mocking
当我弄清楚为什么我的测试文件 运行 很顺利,但如果整个包的测试 运行 与 pytest
在一起时,我就崩溃了。
我有一个 parent class 和一个 child class。 child 像这样调用 parent 的初始化:
class Child(Parent):
__init__(...,**kwargs):
...
super().__init__(**kwargs)
我想用一个小测试来定义这个行为。
from unittest.mock import Mock
def test_init_calls_parent_init():
Parent.__init__ = Mock()
Child()
assert Parent.__init__.called
问题是Parent.__init__
对于其他文件中的所有以下测试,一直是模拟。
我的想法是将它放入函数作用域中只是一个临时更改。当然,由于这些测试失败了,它们隐含地定义了对 parent init 的需求,但我也想通过一个显式测试来确保。
我应该创建一些 pytest
setup/teardown 或者什么是防止这种情况的公认方法?
您可以尝试将父级转换为 fixture, which defaults to having a "function" scope,理论上应该在每次测试后拆除 fixture。
记得yield your fixture并在之后添加任何其他必要的拆卸说明。
你的 Parent 不解构模拟的原因是因为父 classes/functions 不是 fixtures 被视为任何其他 parent/child 函数。
当您执行 Parent.__init__ = Mock()
时,您基本上重新定义了模块本身的 __init__
,然后反映在后续测试中。
与其手动将 Parent.__init__
的实现更改为 Mock
,我的建议是只使用 unittest and pytest-mock.
中已有的修补功能
- 我们也可以按照@MattSom 的建议使用monkeypatch(见评论部分)
src.py
class Parent:
def __init__(self, **kwargs):
print("Parent __init__ called")
class Child(Parent):
def __init__(self, **kwargs):
super().__init__(**kwargs)
print("Child __init__ called")
没有更正:
from unittest.mock import Mock, patch
from src import Child, Parent
def test_init_calls_real_parent_init():
Child()
def test_init_calls_updated_parent_init():
Parent.__init__ = Mock()
Child()
assert Parent.__init__.called
def test_init_calls_real_parent_init_2():
Child()
输出:
$ pytest -q test_src.py -rP
... [100%]
================================================================================================= PASSES ==================================================================================================
____________________________________________________________________________________ test_init_calls_real_parent_init _____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Parent __init__ called
Child __init__ called
___________________________________________________________________________________ test_init_calls_updated_parent_init ___________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
___________________________________________________________________________________ test_init_calls_real_parent_init_2 ____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
3 passed in 0.01s
调查结果:
第一个测试称为真正的Parent.__init__
。第二次测试称为模拟。然而,在第三次测试中,它也意外地调用了第二次测试中制作的模拟。
修正后:
from unittest.mock import Mock, patch
from src import Child, Parent
def test_init_calls_real_parent_init():
Child()
# Personally I wouldn't advise to do this. It just works :)
def test_init_calls_updated_parent_init():
# Setup
orig_parent_init = Parent.__init__ # Store original init
# Real test
Parent.__init__ = Mock()
Child()
assert Parent.__init__.called
# Teardown
Parent.__init__ = orig_parent_init # Bring back the original init
@patch("src.Parent.__init__") # Uses unittest
def test_init_calls_mocked_parent_init(mock_parent_init):
Child()
assert mock_parent_init.called
def test_init_calls_mocked_parent_init_2(mocker): # Uses pytest-mock
mock_parent_init = mocker.patch("src.Parent.__init__")
Child()
assert mock_parent_init.called
def test_init_calls_real_parent_init_2():
Child()
输出:
$ pytest -q test_src_2.py -rP
..... [100%]
================================================================================================= PASSES ==================================================================================================
____________________________________________________________________________________ test_init_calls_real_parent_init _____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Parent __init__ called
Child __init__ called
___________________________________________________________________________________ test_init_calls_updated_parent_init ___________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
___________________________________________________________________________________ test_init_calls_mocked_parent_init ____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
__________________________________________________________________________________ test_init_calls_mocked_parent_init_2 ___________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
___________________________________________________________________________________ test_init_calls_real_parent_init_2 ____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Parent __init__ called
Child __init__ called
5 passed in 0.03s
调查结果:
这里我使用了2种方案:
- 要么在手动重新分配给 mock 后改回
Parent.__init__
的原始实现(参见 test_init_calls_updated_parent_init
)(不建议)
- 或者使用 unittest 和 pytest-mock 的内置补丁功能(参见
test_init_calls_mocked_parent_init
和 test_init_calls_mocked_parent_init_2
)
现在,第一个和最后一个测试都正确地调用了实际的 Parent.__init__
,即使在进行了所有模拟之后也是如此。
当我弄清楚为什么我的测试文件 运行 很顺利,但如果整个包的测试 运行 与 pytest
在一起时,我就崩溃了。
我有一个 parent class 和一个 child class。 child 像这样调用 parent 的初始化:
class Child(Parent):
__init__(...,**kwargs):
...
super().__init__(**kwargs)
我想用一个小测试来定义这个行为。
from unittest.mock import Mock
def test_init_calls_parent_init():
Parent.__init__ = Mock()
Child()
assert Parent.__init__.called
问题是Parent.__init__
对于其他文件中的所有以下测试,一直是模拟。
我的想法是将它放入函数作用域中只是一个临时更改。当然,由于这些测试失败了,它们隐含地定义了对 parent init 的需求,但我也想通过一个显式测试来确保。
我应该创建一些 pytest
setup/teardown 或者什么是防止这种情况的公认方法?
您可以尝试将父级转换为 fixture, which defaults to having a "function" scope,理论上应该在每次测试后拆除 fixture。
记得yield your fixture并在之后添加任何其他必要的拆卸说明。
你的 Parent 不解构模拟的原因是因为父 classes/functions 不是 fixtures 被视为任何其他 parent/child 函数。
当您执行 Parent.__init__ = Mock()
时,您基本上重新定义了模块本身的 __init__
,然后反映在后续测试中。
与其手动将 Parent.__init__
的实现更改为 Mock
,我的建议是只使用 unittest and pytest-mock.
- 我们也可以按照@MattSom 的建议使用monkeypatch(见评论部分)
src.py
class Parent:
def __init__(self, **kwargs):
print("Parent __init__ called")
class Child(Parent):
def __init__(self, **kwargs):
super().__init__(**kwargs)
print("Child __init__ called")
没有更正:
from unittest.mock import Mock, patch
from src import Child, Parent
def test_init_calls_real_parent_init():
Child()
def test_init_calls_updated_parent_init():
Parent.__init__ = Mock()
Child()
assert Parent.__init__.called
def test_init_calls_real_parent_init_2():
Child()
输出:
$ pytest -q test_src.py -rP
... [100%]
================================================================================================= PASSES ==================================================================================================
____________________________________________________________________________________ test_init_calls_real_parent_init _____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Parent __init__ called
Child __init__ called
___________________________________________________________________________________ test_init_calls_updated_parent_init ___________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
___________________________________________________________________________________ test_init_calls_real_parent_init_2 ____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
3 passed in 0.01s
调查结果:
第一个测试称为真正的Parent.__init__
。第二次测试称为模拟。然而,在第三次测试中,它也意外地调用了第二次测试中制作的模拟。
修正后:
from unittest.mock import Mock, patch
from src import Child, Parent
def test_init_calls_real_parent_init():
Child()
# Personally I wouldn't advise to do this. It just works :)
def test_init_calls_updated_parent_init():
# Setup
orig_parent_init = Parent.__init__ # Store original init
# Real test
Parent.__init__ = Mock()
Child()
assert Parent.__init__.called
# Teardown
Parent.__init__ = orig_parent_init # Bring back the original init
@patch("src.Parent.__init__") # Uses unittest
def test_init_calls_mocked_parent_init(mock_parent_init):
Child()
assert mock_parent_init.called
def test_init_calls_mocked_parent_init_2(mocker): # Uses pytest-mock
mock_parent_init = mocker.patch("src.Parent.__init__")
Child()
assert mock_parent_init.called
def test_init_calls_real_parent_init_2():
Child()
输出:
$ pytest -q test_src_2.py -rP
..... [100%]
================================================================================================= PASSES ==================================================================================================
____________________________________________________________________________________ test_init_calls_real_parent_init _____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Parent __init__ called
Child __init__ called
___________________________________________________________________________________ test_init_calls_updated_parent_init ___________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
___________________________________________________________________________________ test_init_calls_mocked_parent_init ____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
__________________________________________________________________________________ test_init_calls_mocked_parent_init_2 ___________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Child __init__ called
___________________________________________________________________________________ test_init_calls_real_parent_init_2 ____________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Parent __init__ called
Child __init__ called
5 passed in 0.03s
调查结果:
这里我使用了2种方案:
- 要么在手动重新分配给 mock 后改回
Parent.__init__
的原始实现(参见test_init_calls_updated_parent_init
)(不建议) - 或者使用 unittest 和 pytest-mock 的内置补丁功能(参见
test_init_calls_mocked_parent_init
和test_init_calls_mocked_parent_init_2
)
现在,第一个和最后一个测试都正确地调用了实际的 Parent.__init__
,即使在进行了所有模拟之后也是如此。