当我弄清楚为什么我的测试文件运行顺利但如果使用 pytest
对整个包运行测试时就会中断,我几乎要疯了。
我有一个父类和一个子类。子进程调用父进程的 init,如下所示:
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__
始终保持模拟。
我的想法是,将其放入函数作用域中只会使其成为临时更改。当然,由于这些测试失败了,它们隐式地定义了对父 init 的需求,但我也想通过一个显式测试来确定。
我应该创建一些pytest
设置/拆卸或者什么是可以接受的方法来防止这种情况?
最佳答案
当您执行 Parent.__init__ = Mock()
时,您基本上重新定义了模块本身的 __init__
,然后反射(reflect)在后续的测试中。
我的建议是使用 unittest 中已有的修补功能,而不是手动将 Parent.__init__
的实现更改为 Mock
。和 pytest-mock .
- 我们还可以使用monkeypatch正如@MattSom 所建议的(参见评论部分)
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
调查结果:
这里我使用了两种解决方案:
- 手动重新分配为模拟后,更改回
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__
。
关于python - 如何恢复模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68576703/