Как правильно протестировать функцию, которая заполняет комбинированный список из базы данных - PullRequest
1 голос
/ 26 июня 2019

Я использую PyQt5 для своего IHM. Это форма. Мне нужно заполнить комбо-бокс данными в базе данных.

На данный момент это имя класса Ui_dialog. У меня два вопроса.

  1. Как правильно выполнить модульное тестирование этих функций? Я действительно не знаю, как начать свои тесты иногда.

  2. Нужно ли мне использовать ресурсы моего проекта (база данных, xmls и т. Д.) Или мне нужно сделать некоторые специальные данные для моего теста, чтобы охватить все возможные случаи?

def fill_combobox_from_database(self,name_of_table,name_of_field,combobox):
    connexion = sqlite3.connect(os.path.dirname(os.path.abspath(__file__))+"\\"+Ui_dialog.NAME_DB)
    cursor = connexion.cursor()
    try:
        request = "select {0} from {1}".format(name_of_field,name_of_table)
        results = cursor.execute(request)
        for row in results:
            combobox.addItem(row[0])
    except Exception as e:
        print(e)

Ответы [ 2 ]

0 голосов
/ 26 июня 2019

Во-первых, да, если вы хотите создать настоящий функциональный тест, вы должны создать что-то, что проверяет полный гамбит тестируемой функции. Он должен включать в себя хорошие данные, пограничные данные, плохие данные и / или в основном все, что, по вашему мнению, оно может получить из своего источника.

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

Как таковая, ваша функция должна включать вызов функции, которая просто делает вызов, такой как

def fill_combobox(self,combobox):    
    results = GetThisRecSet(parameter(s))

    for row in results:
        combobox.addItem(row[0])

Без каких-либо ссылок на SQL или что-либо, что является неотъемлемой частью серверной части. Теперь параметр (ы) может быть нужным вам FieldName или списком FieldNames, но они даже не обязательно должны быть FieldNames, найденными в базе данных, и вам определенно не следует ссылаться на таблицу. Внешний интерфейс (как было сказано) должен быть в основном полностью неосведомлен о серверной части, он просто отправляет то, что хочет, и промежуточный уровень переводит это и получает все, что от серверной части, а затем передает это интерфейсной стороне в формате он ожидает независимо от того, что это за формат.

Обычно я передаю набор записей, и этого достаточно, но, возможно, вы не хотите этого делать, а вместо этого хотите передать что-то еще в графический интерфейс. Что бы это ни было, четко определите, что это будет, и создайте свою фиктивную функцию соответственно. Затем, когда наступит время, чтобы подключить все, вы просто измените фиктивную функцию, чтобы получить данные из фактического источника и поместить эти данные в формат, который вы хотите передать в графический интерфейс. Это также можно использовать в качестве функционального теста, предоставляя данные, с которыми вы хотите, чтобы GUI работал, и таким образом вы точно знаете, что он получает, и это просто вопрос plug-in-play - отцепите настоящий класс и замените функционал тестирование класса, поскольку оба имеют одинаковый набор хуков, только в тесте используются статические значения или полустатические значения, или что бы то ни было определено, необходимо создавать функции типа, которые затем можно использовать снова и снова, чтобы проверить, не было ли что-то сломано при изменении были сделаны на передний конец.

Например (как указано), я часто передаю наборы записей (когда это применимо), так как здесь есть фиктивная функция класса, которую я мог бы создать, чтобы сделать именно это.

class MyDatabase:
    # This will handle everything pertinent to the Database
    def __init__(self,pathfilename):
        self.__dbasePathName = pathfilename

    def GetData(self):
        DataRecSet = [
            {'Field1':'Value1-1', 'Field2':'Value1-2'} ,
            {'Field1':'Value2-1', 'Field2':'Value2-2'} ,
            {'Field1':'Value3-1', 'Field2':'Value3-2'}
            ]

        return DataRecSet 

Тогда все, что мне нужно сделать, - это создать экземпляр MyDatabase, как обычно, и делать вызовы этих фиктивных функций, пока я не буду готов подключить его к реальному бэкэнду. И наоборот, я могу протестировать серверную часть аналогичным образом со Среднего уровня, даже не заботясь о том, как эти данные будут отображаться.

В качестве заключительного замечания я склоняюсь к созданию этих классов функциональных тестов по мере необходимости, поскольку мне нужно иметь возможность тестировать различные части графического интерфейса пользователя по мере его построения, а затем, как только я это сделаю, я немного его убираю и сохраняю как мой фактический функциональный тестовый класс.

0 голосов
/ 26 июня 2019

Для начала вы должны переместить создание этого курсора в отдельную функцию, где оно должно быть проверено (ниже я назвал его get_cursor).

Затем вы должны смоделировать метод execute.Я предлагаю использовать pytest и pytest-mock .

def test_fill_combobox_from_database_calls_execute(mocker):   
    mocked = mocker.patch("my_module.get_cursor")
    TABLE = "foo"
    FIELD = "bar"
    mocked_combo = mocker.MagicMock()
    REQUEST = "select {0} from {1}".format(FIELD, TABLE)
    MyApp().fill_combobox_from_database(TABLE, FIELD, mocked_combo)
    mocked.return_value.execute.assert_called_once()
    mocked.return_value.execute.assert_called_with(REQUEST)

После этого вы должны проверить, вызывается ли метод additem в combobox с правильными аргументами:

def test_fill_combobox_from_database_calls_combobox_additem(mocker):   
    SAMPLE = [("foo",), ("bar",), ("foobar",)]
    mocked_cursor = mocker.patch("my_module.get_cursor")
    mocked_cursor.return_value.execute.return_value = SAMPLE
    TABLE = "foo"
    FIELD = "bar"
    mocked_combo = mocker.MagicMock()
    REQUEST = "select {0} from {1}".format(FIELD, TABLE)
    MyApp().fill_combobox_from_database(TABLE, FIELD, mocked_combo)
    for item in SAMPLE:
        calls = [mocker.call(item[0])]
        mocked_combo.addItem.assert_has_calls(calls, any_order=True)

а также вы должны проверить исключение:

def test_fill_combobox_from_database_on_exception(mocker):   
    mocked_exception = Exception()
    mocker.patch("my_module.get_cursor")
    mocked_cursor.return_value.execute.side_effect = mocked_exception
    mocked = mocker.patch("my_module.print")
    MyApp().fill_combobox_from_database("foo", "bar", mocker.MagicMock())
    mocked.assert_called_once()
    mocked.assert_called_with(mocked_exception)
...