pytest Monkeypatch 类方法,同时调用 super

问题描述 投票:0回答:2

我有一个模块

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
python pytest
2个回答
1
投票

修改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"

-1
投票

通过内置库 unittest 或外部库 pytest-mock 使用 patchpatch.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
© www.soinside.com 2019 - 2024. All rights reserved.