我有一个模块
loaders
,其中包含类“Loader”和类方法“load”。在测试期间,我想在“Loader.load”中附加一些额外的步骤,以考虑测试特定数据的后处理,因此本质上是覆盖它。我该如何正确地做到这一点?
我尝试创建一个继承Loader并使用monkeypatch.setattr(“loaders.Loader”,mock_loader)的模拟类,但这仅在我运行一个测试时有效,但在我运行所有测试时无效。
装载机.py
class Loader:
def load():
# do something
return data
测试.py
from loaders import Loader
class MockLoader(Loader):
def load():
data = super().load()
# do something to data
return data
def test_loader_special1(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
#run test logic 1
def test_loader_special2(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
#run test logic 2
修改Niels的答案也发布在这里以使用
monkeypatch
:
@pytest.fixture
def mock_load(monkeypatch):
real_func = Loader.load
def mock_func(self, *args, **kwargs):
print("Mock load() called")
data = real_func(self, *args, **kwargs)
data += " and mock"
return data
monkeypatch.setattr(Loader, "load", mock_func)
然后,您可以使用以下命令标记要为测试模块中的所有测试加载的夹具:
@pytest.fixture(autouse=True)
或将测试标记为使用指定的夹具:
@pytest.mark.usefixtures("mock_load")
def test_loader_special1():
...
如果你想模拟整个类,你需要在导入类之前模拟它才能生效,或者你可以只导入模块,这样你就不会到处都是导入语句:
import loaders
class MockLoader(loaders.Loader):
def load(self):
data = super().load()
data += " and mock"
return data
def test_loader_special1(monkeypatch):
monkeypatch.setattr("loaders.Loader", MockLoader)
data = loaders.Loader().load()
print(f"{data=}")
assert data == "real and mock"
通过内置库 unittest 或外部库 pytest-mock 使用 patch 或 patch.object :
patch() 充当函数装饰器、类装饰器或上下文 经理。在函数体内或 with 语句中,目标 已用新对象修补。
明确记录该补丁仅适用于每个测试:
当函数/with 语句退出时,补丁将被撤消。
该补丁将用于包装您的实际实现,以便您可以在调用它之前和/或之后执行任何必要的步骤。
loader.py
class Loader:
def load(self):
print("Real load() called")
return "real"
test_loaders.py
import pytest
from unittest.mock import patch
from loaders import Loader
@pytest.fixture
def mock_load(mocker):
real_func = Loader.load
def mock_func(self, *args, **kwargs):
print("Mock load() called")
data = real_func(self, *args, **kwargs)
data += " and mock"
return data
# Option 1: Using pytest-mock + new
mocker.patch.object(Loader, 'load', new=mock_func)
"""
Alternative ways of doing Option 1. All would just work the same.
# Option 2: Using pytest-mock + side_effect
mocker.patch.object(Loader, 'load', side_effect=mock_func, autospec=True)
# Option 3: Using unittest + new
with patch.object(Loader, 'load', new=mock_func):
yield
# Option 4: Using unittest + new
with patch.object(Loader, 'load', side_effect=mock_func, autospec=True):
yield
"""
def test_loader_special1(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
def test_loader_special2(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
def test_loader_special3():
data = Loader().load()
print(f"{data=}")
assert data == "real"
def test_loader_special4(mock_load):
data = Loader().load()
print(f"{data=}")
assert data == "real and mock"
输出:
$ pytest test_loaders.py -q -rP
.... [100%]
================================================================================================= PASSES ==================================================================================================
__________________________________________________________________________________________ test_loader_special1 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
__________________________________________________________________________________________ test_loader_special2 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
__________________________________________________________________________________________ test_loader_special3 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Real load() called
data='real'
__________________________________________________________________________________________ test_loader_special4 ___________________________________________________________________________________________
------------------------------------------------------------------------------------------ Captured stdout call -------------------------------------------------------------------------------------------
Mock load() called
Real load() called
data='real and mock'
4 passed in 0.01s