Настройте фиктивную базу данных в Python для модульного тестирования

Я хочу настроить фиктивную базу данных (в отличие от создания тестовой базы данных, если это возможно), чтобы проверить, правильно ли запрашиваются данные, а затем преобразуются в фрейм данных Pandas. У меня есть некоторый опыт фиктивного и модульного тестирования, и я успешно настроил предыдущий тест. Однако у меня возникают трудности с применением того, как имитировать реальные объекты, такие как базы данных, для тестирования.

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

  • Я внимательно изучил документацию по юнит-тестам и макетам Python, поэтому знаю, что она делает и как работает (по большей части).
  • Я прочитал бесчисленное количество сообщений о насмешках в стеке и за его пределами. Они были полезны для понимания общих концепций и того, что можно сделать в описанных конкретных обстоятельствах, но я не смог заставить это работать в моей ситуации.
  • Я пытался издеваться над различными аспектами функции, включая подключение к базе данных, запрос и использование функции «pd_read_sql (query, con)», но безрезультатно. Я считаю, что это самое близкое, что у меня есть.

Мой последний код для тестирования

import pandas as pd
import pyodbc
import unittest
import pandas.util.testing as tm

from unittest import mock

# Function that I want to test
def p2ctt_data_frame():
    conn = pyodbc.connect(
        r'Driver = {Microsoft Access Driver (*.mdb, *.accdb)};'
        r'DBQ=My\Path\To\Actual\Database\Access Database.accdb;'
    )

    query = 'select * from P2CTT_2016_Plus0HHs'

    # I want to make sure this dataframe object is created as intended
    df = pd.read_sql(query, conn) 

    return df


class TestMockDatabase(unittest.TestCase):

    @mock.patch('directory1.script1.pyodbc.connect')  # Mocking connection
    def test_mock_database(self, mock_access_database):

        # The dataframe I expect as the output after query is run on the 'mock database'
        expected_result = pd.DataFrame({
            'POSTAL_CODE':[
                'A0A0A1'
            ],
            'DA_ID':[
                1001001
            ],
            'GHHDS_DA':[
                100
            ]
        })

        # This is the line that I believe is wrong. I want to create a return value that mocks an Access table
        mock_access_database.connect().return_value = [('POSTAL_CODE', 'DA_ID', 'GHHDS_DA'), ('A0A0A1', 1001001, 100)]

        result = p2ctt_data_frame()  # Run original function on the mock database

        tm.assert_frame_equal(result, expected_result) 


if __name__ == "__main__":
    unittest.main()

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

В настоящее время, если я распечатаю результат при попытке издеваться над базой данных, я получаю:

Пустой фрейм данных Столбцы: [] Показатель: []

Кроме того, я получаю следующую ошибку после запуска теста:

AssertionError: DataFrame разные;
Несоответствие формы DataFrame [слева]: (0, 0) [справа]: (1, 3)

Почему бы не запросить базу данных, а использовать только 3 верхних значения? При тестировании на точность убедитесь, что имена столбцов верны, а некоторые другие аспекты фрейма данных, которые вы ищете, например, почтовый индекс длиной 6 символов.

Daniel Butler 17.04.2019 06:23

Привет, спасибо за ответ! Вы бы сказали, что это лучший способ тестирования баз данных в целом? Обычно я все еще не уверен в модульных тестах и ​​интеграционных тестах, и нужно ли их тестировать одинаково.

ShockDoctor 17.04.2019 16:27

То, что я нашел, это делать и то, и другое. Эта книга научила меня многому о тестировании и о том, как его правильно проводить: повинуйтесь тестированию козла.com/book/praise.harry.html

Daniel Butler 17.04.2019 17:36

Буду читать, еще раз спасибо!

ShockDoctor 17.04.2019 17:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
10
4
17 371
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я бы разбил его на несколько отдельных тестов. Функциональный тест, подтверждающий, что будет получен желаемый результат, тест, чтобы убедиться, что вы можете получить доступ к базе данных и получить ожидаемые результаты, и окончательный модульный тест о том, как его реализовать. Я бы написал каждый тест именно в таком порядке, сначала выполняя тесты, а затем фактическую функцию. Если я обнаружил, что если я не могу понять, как что-то сделать, я попробую это в отдельном REPL или создам ветку git для работы с ним, а затем вернусь к основной ветке. Дополнительную информацию можно найти здесь: https://obeythetestinggoat.com/book/praise.harry.html

Комментарии для каждого теста и причина этого находятся в коде.

import pandas as pd
import pyodbc

def p2ctt_data_frame(query='SELECT * FROM P2CTT_2016_Plus0HHs;'): # set query as default
    with  pyodbc.connect(
        r'Driver = {Microsoft Access Driver (*.mdb, *.accdb)};'
        r'DBQ=My\Path\To\Actual\Database\Access Database.accdb;'
    ) as conn:  # use with so the connection is closed once completed

        df = pd.read_sql(query, conn)

    return df

Отдельный тестовый файл:

import pandas as pd
import pyodbc
import unittest
from unittest import mock

class TestMockDatabase(unittest.TestCase):

    def test_p2ctt_data_frame_functional_test(self):  # Functional test on data I know will not change
        actual_df = p2ctt_data_frame(query='SELECT * FROM P2CTT_2016_Plus0HHs WHERE DA_ID = 1001001;')

        expected_df = pd.DataFrame({
            'POSTAL_CODE':[
                'A0A0A1'
            ],
            'DA_ID':[
                1001001
            ],
            'GHHDS_DA':[
                100
            ]
        })

        self.assertTrue(actual_df == expected_df)

    def test_access_database_returns_values(self):  # integration test with the database to make sure it works
        with pyodbc.connect(
            r'Driver = {Microsoft Access Driver (*.mdb, *.accdb)};'
            r'DBQ=My\Path\To\Actual\Database\Access Database.accdb;'
        ) as conn:
            with conn.cursor() as cursor:
                cursor.execute("SELECT TOP 1 * FROM P2CTT_2016_Plus0HHs WHERE DA_ID = 1001001;")
                result = cursor.fetchone()

        self.assertTrue(len(result) == 3)  # should be 3 columns by 1 row

        # Look for accuracy in the database
        info_from_db = []
        for data in result:  # add to the list all data in the database
            info_from_db.append(data)

        self.assertListEqual(   # All the information matches in the database
            ['A0A0A1', 1001001, 100], info_from_db
        )


    @mock.patch('directory1.script1.pd')  # testing pandas
    @mock.patch('directory1.script1.pyodbc.connect')  # Mocking connection so nothing sent to the outside
    def test_pandas_read_sql_called(self, mock_access_database, mock_pd):  # unittest for the implentation of the function
        p2ctt_data_frame()
        self.assert_True(mock_pd.called)  # Make sure that pandas has been called
        self.assertIn(
            mock.call('select * from P2CTT_2016_Plus0HHs'), mock_pd.mock_calls
        )  # This is to make sure the proper value is sent to pandas. We don't need to unittest that pandas handles the
        # information correctly.

*Я не смог проверить это, поэтому могут быть некоторые ошибки, которые мне нужно исправить.

Вау, спасибо, это было супер ясно и полезно! Однако один вопрос. Если тест 1 пройден, не означает ли это, что тест 3 не нужен? Единственная причина, по которой я говорю это, заключается в том, что если ожидаемые и фактические кадры данных равны, не означает ли это, что Pandas нужно было вызывать с этим конкретным запросом, что делает тестирование вызова Pandas излишним?

ShockDoctor 17.04.2019 18:59

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

Daniel Butler 17.04.2019 19:08

Другие вопросы по теме