У меня есть скрипт под названием 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 ().






Пример с имитацией курсора для проверки случая, когда набор результатов пуст:
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
Подробности:
MagicMock(spec=Cursor) возвращает класс, имитирующий класс pymongo.cursor.Cursor. db_data_mock является экземпляром этого фиктивного класса.db_data_mock.count.return_value = 0 имитирует метод count, поэтому он всегда возвращает ноль.
Следующие две строки: создайте макет экземпляра для pymongo.mongo_replica_set_client.MongoReplicaSetClient (так же, как в 1.) и подключите к нему макет курсора, чтобы метод find() всегда возвращал экземпляр db_data_mock, который мы создали ранее.
commons.db_connection на фиктивную, которая возвращает наш фиктивный объект MongoReplicaSetClient.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; он даже не распознается unittest.
Однако pytest не заменяет имитирующую функциональность. Единственная функция, доступная из коробки, - это обезьяна, это можно рассматривать как (довольно ограниченную) замену unittest.mock.patch, но нет ничего, что могло бы заменить моки / заглушки. Если по какой-то причине вы не хотите использовать макеты unittest, вам придется написать свои собственные классы макетов / заглушек, но это непродуктивно: вам придется писать и поддерживать гораздо больше тестового кода, чем нужно.
Но я дополню ответ тестом, который не импортирует unittest, если это то, к чему вы стремитесь.
Мое решение:
Поскольку функция 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
Используя библиотеку, нам не нужно писать полное определение класса, и это можно сделать с помощью минимума определений. Он очень помогает в быстром написании различных кейсов.
Привет. Спасибо за ответ, но я использую pytest, а не unittest. Можно ли сделать то же самое с помощью pytest? Я не хочу писать некоторые случаи в pytest, а некоторые в unittest для обеспечения единообразия.