Pytest имитирующие функции .count () и .find () объекта MongoReplicaSetClient

У меня есть скрипт под названием commons.py, который содержит некоторые наиболее часто используемые функции.

В моем основном скрипте я создаю объект подключения mongo:

db_ob = commons.db_connection()

db_connection возвращает объект подключения MongoReplicaSetClient.

как написать тестовые примеры для моей функции ниже ??

def check_entries():
    try:
        db_ob = commons.db_connection()
    except:
        print('Unable to connect to db!!')
        return False
    db_name = 'my_db_name'
    collection_name = 'my_collection_name'
    db_data = db_ob[db_name][collection_name].find()
    if db_data.count() == 0:
        print('No entries found in the collection!')
        return False
    return True

Я могу издеваться над своей функцией db_connection, но у меня возникла проблема с издевательством над функциями .count () и .find ().

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
1 460
2

Ответы 2

Пример с имитацией курсора для проверки случая, когда набор результатов пуст:

from unittest.mock import patch, MagicMock
from pymongo.mongo_replica_set_client import MongoReplicaSetClient
from pymongo.cursor import Cursor
import testee


def test_empty_result_set():
    db_data_mock = MagicMock(spec=Cursor)()  # 1
    db_data_mock.count.return_value = 0  # 2
    db_conn_mock = MagicMock(spec=MongoReplicaSetClient)()  # 3
    db_conn_mock.__getitem__().__getitem__().find.return_value = db_data_mock
    with patch('commons.db_connection', return_value=db_conn_mock):  # 4
        assert not testee.check_entries()  # 5

Подробности:

  1. MagicMock(spec=Cursor) возвращает класс, имитирующий класс pymongo.cursor.Cursor. db_data_mock является экземпляром этого фиктивного класса.
  2. db_data_mock.count.return_value = 0 имитирует метод count, поэтому он всегда возвращает ноль.

  3. Следующие две строки: создайте макет экземпляра для pymongo.mongo_replica_set_client.MongoReplicaSetClient (так же, как в 1.) и подключите к нему макет курсора, чтобы метод find() всегда возвращал экземпляр db_data_mock, который мы создали ранее.

  4. Наконец, замените функцию commons.db_connection на фиктивную, которая возвращает наш фиктивный объект MongoReplicaSetClient.
  5. Все приготовления сделаны; сделать фактический тест.

Обновление: в комментариях был запрошен тест, который не использует unittest

Если по какой-то странной причине «чистоты» вы не хотите трогать код unittest, вам придется либо найти для этого замену библиотеку, либо написать макеты самостоятельно. pytest не предлагает функции имитации "из коробки". Приведенный выше пример без unittest мог бы выглядеть так:

from collections import defaultdict
import testee


class CursorMock:

    def count(self):
        return 0


class ConnectionMock:

    def find(self):
        return CursorMock()


class MongoReplicaSetClientMock:

    def __getitem__(self, name):
        return defaultdict(ConnectionMock)


def db_connection_mock(*args, **kwargs):
    return MongoReplicaSetClientMock()


def test_empty_result_set(monkeypatch):
    monkeypatch.setattr(commons, 'db_connection', db_connection_mock)
    assert not testee.check_entries()

Вместо unittest.mock.patch использовался monkeypatch приспособление.

Обратите внимание, что, хотя есть некоторые плагины для pytest, которые предлагают функции имитации (например, pytest-mock), большинство из тех, что я знаю, являются просто удобными оболочками для unittest.mock и до сих пор используют их под капотом.

Привет. Спасибо за ответ, но я использую pytest, а не unittest. Можно ли сделать то же самое с помощью pytest? Я не хочу писать некоторые случаи в pytest, а некоторые в unittest для обеспечения единообразия.

Shikhar Awasthi 02.05.2018 15:49

Я думаю, вы неправильно понимаете разницу между ними. pytest в основном заменяет unittest в сборе тестов, выполнении тестов, тестовых сигнатурах (функции против классов + методы классов) и утверждениях. Фактически, приведенный выше тест был написан с использованием pytest; он даже не распознается unittest.

hoefling 02.05.2018 16:25

Однако pytest не заменяет имитирующую функциональность. Единственная функция, доступная из коробки, - это обезьяна, это можно рассматривать как (довольно ограниченную) замену unittest.mock.patch, но нет ничего, что могло бы заменить моки / заглушки. Если по какой-то причине вы не хотите использовать макеты unittest, вам придется написать свои собственные классы макетов / заглушек, но это непродуктивно: вам придется писать и поддерживать гораздо больше тестового кода, чем нужно.

hoefling 02.05.2018 16:27

Но я дополню ответ тестом, который не импортирует unittest, если это то, к чему вы стремитесь.

hoefling 02.05.2018 16:29

Мое решение:

Поскольку функция db_connection возвращает объект, я заставил мою фиктивную функцию также возвращать объект со встроенными необходимыми функциями.

Я сделал класс с необходимыми функциями и вернул объект в желаемом формате.

В зависимости от различных случаев (успешное / неудачное соединение, различные значения счетчика и т. д.) Может быть несколько фиктивных функций.

def mock_db_connection_success(*args, **kwargs):
    """Function to mock successful db connection."""
    class ReturnClass(object):
        """."""
        @staticmethod
        def __init__(*args, **kwargs):
            """."""
            pass

        # @staticmethod
        def find(self, *args, **kwargs):
            """."""
            return self

        @staticmethod
        def count(*args, **kwargs):
            """."""
            return 1
    class_ob = ReturnClass()
    return {"my_db_name": {"my_collection_name": class_ob}}


@mock.patch('my_script.commons.db_connection', side_effect=mock_db_connection_success)
def test_function(*args, **kwargs):
    """Test Function."""
    # use the function here

Я также нашел реализацию того же с использованием библиотеки Python: Flexmock

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

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