Я хочу настроить фиктивную базу данных (в отличие от создания тестовой базы данных, если это возможно), чтобы проверить, правильно ли запрашиваются данные, а затем преобразуются в фрейм данных Pandas. У меня есть некоторый опыт фиктивного и модульного тестирования, и я успешно настроил предыдущий тест. Однако у меня возникают трудности с применением того, как имитировать реальные объекты, такие как базы данных, для тестирования.
В настоящее время у меня возникают проблемы с получением результата при запуске моего теста. Я считаю, что я неправильно издеваюсь над объектом базы данных, я пропускаю какой-то шаг или мой мыслительный процесс неверен. Я помещаю свои тесты и мой код для тестирования в один и тот же сценарий, чтобы упростить ситуацию.
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)
Привет, спасибо за ответ! Вы бы сказали, что это лучший способ тестирования баз данных в целом? Обычно я все еще не уверен в модульных тестах и интеграционных тестах, и нужно ли их тестировать одинаково.
То, что я нашел, это делать и то, и другое. Эта книга научила меня многому о тестировании и о том, как его правильно проводить: повинуйтесь тестированию козла.com/book/praise.harry.html
Буду читать, еще раз спасибо!
Я бы разбил его на несколько отдельных тестов. Функциональный тест, подтверждающий, что будет получен желаемый результат, тест, чтобы убедиться, что вы можете получить доступ к базе данных и получить ожидаемые результаты, и окончательный модульный тест о том, как его реализовать. Я бы написал каждый тест именно в таком порядке, сначала выполняя тесты, а затем фактическую функцию. Если я обнаружил, что если я не могу понять, как что-то сделать, я попробую это в отдельном 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 излишним?
В данном случае это немного избыточно, потому что мы рассматриваем эти функции в вакууме. Но допустим, вам нужно проверить запрос, потому что он использует пользовательский ввод. Мы хотели бы создать отдельную validate_query
в нашей p2ctt_data_frame
функции для ее обработки. При тестировании p2ctt_data_frame
я бы издевался над validate_query
в unittest, а затем убеждался, что он вызывается. Как только это будет сделано, создайте отдельный модульный тест, чтобы validate_query
убедиться, что он делает то, что должен.
Почему бы не запросить базу данных, а использовать только 3 верхних значения? При тестировании на точность убедитесь, что имена столбцов верны, а некоторые другие аспекты фрейма данных, которые вы ищете, например, почтовый индекс длиной 6 символов.