Можно ли исправить метод, вызванный созданным ранее макетом? - PullRequest
0 голосов
/ 07 мая 2020

У меня есть приспособление, которое создает mocker.Mock вместо gui.Menu объекта во время инициализации gui.Buttons. Ссылка хранится в атрибуте 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()

С реальным объектом я мог бы написать (True, 'project.gui.Menu.show_error') внутри @pytest.mark.parametrize вместо (True, 'pytest.Mock.show_error'), что не работает и дает ModuleNotFoundError: No module named 'pytest.Mock'.

Интересно, возможно ли это to 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

1 Ответ

1 голос
/ 09 мая 2020

Я не вижу возможности справиться с этим в том же тесте - вам, вероятно, понадобится отдельный тест для последнего вызова. Проблема в том, что меню уже имитировано, и вам понадобится этот конкретный c макет меню для проверки вызова функции (функция будет вызываться из этого макета).
Вот возможная рабочая реализация:

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()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...