Как смоделировать импортированный объект с помощью pytest-mock или magicmock - PullRequest
6 голосов
/ 14 октября 2019

Я пытаюсь понять возможности 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 ]

3 голосов
/ 29 октября 2019

Я считаю, что вы правы, не проверяя случаи на реальной базе данных, потому что это больше не модульное тестирование, если вы используете внешние зависимости.

Существует возможность указать 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 ** (я предполагаю, что в каждом тесте это другой путь), а не там, где он определен.

2 голосов
/ 29 октября 2019

Чтобы ответить на первый вопрос «Как смоделировать импортированный объект с помощью 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(...)

заметьте, что вы не исправляете фактический источник БД

Для другого варианта использования

Я хочу смоделировать базу данных (используя базу данных mongo), более конкретно объект "db_conn"

Вы также следуете подсказкам по ссылке выше:

mock.patch('some_module1.db_conn', mock.MagicMock(return_value=...))

Учитывая это, в своих тестах вы заметите, что db из `db = db_conn.db_name2.db_collection2 'создаст еще один фиктивный объект. Звонки на этот объект также будут записаны. Таким образом, вы также сможете отслеживать историю вызовов и присвоений значений.


Более того, см. пример того, как pach mongo db.

Для тестирования приложений Flask см. документацию по flask . Также это также хорошее объяснение и использует соединения с БД .

Как общий совет, как упомянуто @MikeMajara - разделите ваш код на более мелкие функции, которые также легко тестировать. Традиционно для TDD: сначала пишите тесты, потом внедряйте и рефакторируйте (особенно DRY!)

1 голос
/ 29 октября 2019

Разделение задач. Создание методов, которые выполняют одно и только одно. Даже больше, если вы собираетесь с 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 . (хотя в идеале вы бы тестировали монго с реальным экземпляром в отдельном тесте).

...