Как смоделировать импортированный объект с помощью pytest-mock или magicmock
Я пытаюсь понять mock/monkeypatch/pytest-mock
возможности.
Сообщите мне, возможно ли это. Если нет, не могли бы вы подсказать, как я могу протестировать этот код.
Моя структура кода:
/
./app
../__init__.py
../some_module1
.../__init__.py
../some_module2
.../__init__.py
./tests
../test_db.py
В /app/__init__.py
Здесь мое приложение (приложение Flask, если оно помогает) запускается вместе с инициализацией объекта подключения к базе данных в базе данных MongoDB:
# ...
def create_app():
# ...
return app
db_conn = DB()
В some_module1
а также some_module
импортировать db_conn
объект и использовать его как часть своих функций:
## some_module1/__init__.py
from app import db_conn
...
db = db_conn.db_name2.db_collection2
def some_func1():
data = db.find()
# check and do something with data
return boolean_result
...
## some_module2/__init__.py
from app import db_conn
...
db = db_conn.db_name1.db_collection1
def some_func2():
data = db.find()
# check and do something with data
return boolean_result
...
В своих тестах я хочу проверить, правильно ли работает мой код на основе данных, полученных из базы данных. Я хочу издеваться над базой данных, в частностиdb_conn
объект, поскольку я не хочу использовать настоящую базу данных (что потребовало бы много работы по настройке среды и ее обслуживанию).
Любые предложения о том, как я могу подражать db_conn
?
Я исследовал pytest-mock
а также magicmock
но я не думаю и не знаю, как издеваться над db_conn
в моем тесте.
3 ответа
Я считаю, что вы правы, не тестируя кейсы на реальной базе данных, потому что это больше не модульное тестирование, если вы используете внешние зависимости.
Есть возможность указатьreturn-value
и настроить его (даже разные возвращаемые значения на каждой итерации) дляMock
или MagicMock
объекты.
from unittest.mock import Mock, patch
from app import db_conn
@patch('app.db_conn.find')
def test_some_func1(db_con_mock):
...
assert ...
Имейте в виду, что в каждом patch
вы должны указать путь импорта db_conn
- путь, по которомуdb_conn
** используется (я предполагаю, что это другой путь в каждом тесте), а не там, где он определен.
Чтобы ответить на основной вопрос "Как имитировать импортированный объект с помощью pytest-mock или magicmock", вы можете сделать:
from unittest import mock # because unittest's mock works great with pytest
def test_some_func1():
with mock.patch('some_module1.db', mock.MagicMock(return_value=...)) as magicmock:
result = some_func1(...)
assert ... e.g. different fields of magicmock
assert expected == result
# or alternatively use annotations
@mock.patch('some_module2.db', mock.MagicMock(return_value=...))
def test_some_func2():
result = some_func2(...)
обратите внимание, что вы не исправляете фактический источник db
Для вашего другого варианта использования
Я хочу имитировать базу данных (используя базу данных mongo), а точнее объект "db_conn"
вы аналогичным образом следуете подсказкам по ссылке выше:
mock.patch('some_module1.db_conn', mock.MagicMock(return_value=...))
Учитывая это, вы заметите в своих тестах, что db
from `db = db_conn.db_name2.db_collection2'создаст еще один фиктивный объект. Звонки на этот объект также будут записаны. Таким образом, вы сможете отслеживать историю звонков и присвоение значений.
Кроме того, посмотрите пример подключения mongo db.
Для тестирования приложений Flask см. Документацию по flask. Также это хорошее объяснение и использует соединения с БД.
В качестве общего совета, например, упомянутого @MikeMajara, разделите свой код на более мелкие функции, которые также легко проверить. По традиции TDD: сначала пишите тесты, потом реализуйте и рефакторинг (особенно DRY!)
Разделение проблем. Создавайте методы, которые делают одно и только одно. Даже больше, если вы собираетесь использовать TDD. В вашем примере some_func2 делает более одного. Вы можете провести рефакторинг следующим образом:
def get_object_from_db():
return db.find()
def check_condition_on_object(obj):
check something to do with object
return true or false
def some_func2():
obj = get_object_from_db()
check_condition_on_object(obj)
При таком подходе вы можете легко протестировать get_object_from_db
а также check_condition_on_object
раздельно. Это улучшит читаемость, позволит избежать ошибок и поможет обнаружить их, если они появятся в какой-то момент.
Насчет "издевательства над импортированным объектом". Возможно, вы пытаетесь имитировать объект с помощью библиотеки, которая предназначена для более сложных случаев, чем ваша. Эти библиотеки предоставляют вам набор методов, окружающих тестовую среду, из коробки, которые могут вам не понадобиться. Судя по внешнему виду, вы просто хотите заполнить объект фиктивными данными и / или взаимодействовать с фиктивным экземпляром db_connection. Так...
Чтобы заполнить, я бы упростил: вы знаете условие, которое хотите проверить, и хотите проверить, является ли результат для данного объекта ожидаемым. Просто создай себеtest_object_provider.py
который возвращает ваши известные случаи для true|false
. Не нужно усложнять вещи.
Чтобы использовать поддельное соединение MongoDB, вы можете попробовать mongomock. (хотя в идеале вы бы протестировали mongo с реальным экземпляром в отдельном тесте).