是否可以修补以前创建的模拟上调用的方法?

2024-04-25 20:53:04 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个fixture,它在初始化gui.Buttons期间创建mocker.Mock而不是gui.Menu对象。引用存储在Buttons.menu属性中。在我的测试中,我检查是否在gui.Buttons.add中调用了正确的函数。参数化测试运行良好,直到我满足了应该调用gui.Menu方法的条件。现在有一个模拟

import pytest
from project import gui

@pytest.fixture
def buttons(mocker):
    mocker.patch('project.gui.tk.Frame.__init__', return_value=None)
    mocker.patch('project.gui.tk.Button')
    return gui.Buttons(mocker.Mock())


@pytest.mark.parametrize('value,patched',(
        (None, 'project.gui.show_no_connection'),
        (False, 'project.gui.Buttons.process_data'),
        (True, 'pytest.Mock.show_error'),
))
def test_add_calls_function(buttons, value, patched, mocker):
    mocker.patch('project.gui.Buttons.exist_check', return_value=value)
    mocked = mocker.patch(patched)
    buttons.add()
    mocked.assert_called_once()

使用real-object,我可以在@pytest.mark.parametrize内写入(True, 'project.gui.Menu.show_error'),而不是(True, 'pytest.Mock.show_error')它不工作并生成ModuleNotFoundError: No module named 'pytest.Mock'

我想知道是否有可能在我的装置中patch创建模拟对象,使其像其他参数化示例一样工作。有可能吗?如果我对它的理解是错误的,请纠正我

测试代码如下所示:

import tkinter as tk
import tkinter.messagebox as msg

from project.connection import Database


def show_no_connection():
    msg.showerror('Error', 'Could not perform operation. Try again later.')


class Menu(tk.Tk):

    def __init__(self):
        super().__init__()
        self.form = Form()

    def show_error(self, message):
        self.form.clear()
        msg.showerror('Error', message)


class Form(tk.Frame):

    def clear(self):
        print('Clearing...')

    def get(self):
        return {'Title': 'Test', 'ISBN': 87327837823}


class Buttons(tk.Frame):

    def __init__(self, menu):
        super().__init__(menu)
        self.menu = menu

    def process_data(self, data, operation):
        operation(data)

    def add(self):
        data = self.menu.form.get()
        exists = self.exist_check(data.get('ISBN', None))
        if exists is None:
            show_no_connection()
        else:
            if exists:
                self.menu.show_error(
                    'Record with set ISBN already exists in database.')
            else:
                self.process_data(data, Database().add)

    @staticmethod
    def exist_check(number):
        if number:
            return Database().search({'ISBN': number})
        return False

显示错误:

=================================== FAILURES ===================================
_________ test_add_calls_function[True-project.gui.Gui.show_error] _________

buttons = <[AttributeError("'Buttons' object has no attribute '_w'") raised in repr()] Buttons object at 0x7f840114aa10>
value = True, patched = 'project.gui.Gui.show_error'
mocker = <pytest_mock.plugin.MockFixture object at 0x7f840114ab90>

    @pytest.mark.parametrize('value,patched',(
            (None, 'project.gui.show_no_connection'),
            (False, 'project.gui.Buttons.process_data'),
            (True, 'project.gui.Gui.show_error'),
    ))
    def test_add_calls_function(buttons, value, patched, mocker):
        mocker.patch('project.gui.Buttons.exist_check', return_value=value)
        mocked = mocker.patch(patched)
        buttons.add()
>       mocked.assert_called_once()
E       AssertionError: Expected 'show_error' to have been called once. Called 0 times.

tests/test_gui_buttons.py:88: AssertionError

Tags: selfprojectadddatapytestvaluedefshow
1条回答
网友
1楼 · 发布于 2024-04-25 20:53:04

我看不出有可能在同一个测试中处理这个问题-您可能需要为最后一个调用进行单独的测试。问题是菜单已经被模拟,您需要特定的菜单模拟来测试函数调用(函数将从该模拟实例调用)。
以下是一个可能的工作实现:

import pytest

# don't use "from project import gui" here to not make a copy in the test module
# that would be used instead of the mocked one
import project.gui  

@pytest.fixture
def menu_mock(mocker):
    # gives the possibility to access the menu mock
    # we need the return_value to get the instance instead of the class
    return mocker.patch('project.gui.Menu').return_value

@pytest.fixture
def buttons(mocker, menu_mock):
    mocker.patch('project.gui.tk.Frame.__init__', return_value=None)
    mocker.patch('project.gui.tk.Button')
    return project.gui.Buttons(menu_mock)


@pytest.mark.parametrize('value, patched',(
        (None, 'project.gui.show_no_connection'),
        (False, 'project.gui.Buttons.process_data')
))
def test_add_calls_function(buttons, value, patched, mocker):
    # unchanged except for the missing parametrize case
    mocker.patch('project.gui.Buttons.exist_check', return_value=value)
    mocked = mocker.patch(patched)
    buttons.add()
    mocked.assert_called_once()


def test_add_calls_show_error(buttons, menu_mock, mocker):
    mocker.patch('project.gui.Buttons.exist_check', return_value=True)
    buttons.add()
    # you now have access to the mocked menu instance
    menu_mock.show_error.assert_called_once()

相关问题 更多 >