У меня есть приложение Python, использующее pytest. Для нескольких моих тестов есть вызовы API к Elasticsearch (с использованием elasticsearch-dsl-py), которые замедляют мои тесты, которые я хотел бы:
yield
.Это в основном вдохновлено pytest-django, где вы должны использовать маркер django_db
, чтобы подключиться к базе данных (но они выдают ошибку, если вы пытаетесь подключиться к БД, тогда как я просто не хочу, чтобы вызов в первое место, вот и все).
Например:
def test_unintentionally_using_es():
"""I don't want a call going to Elasticsearch. But they just happen. Is there a way to "mock" the call? Or even just prevent the call from happening?"""
@pytest.mark.elastic
def test_intentionally_using_es():
"""I would like for this marker to perform some tasks beforehand (i.e. clear the indices)"""
# To replicate that second test, I currently use a fixture:
@pytest.fixture
def elastic():
# Pre-test tasks
yield something
Я думаю, что это вариант использования маркеров, верно? В основном вдохновлен pytest-django.
Для издевательства над elasticsearch, проверьте, например. эластичный макет
Спасибо @hoefling Ах, я думаю, что декоратор usefixtures
идеально подходит для второго случая, а не маркеры. Но как предотвратить вызовы ES по умолчанию (например, когда маркеры/фикстуры не применяются)?
Думаю, теперь я понимаю, что вы имеете в виду, скоро добавлю ответ.
Ваш первоначальный подход с комбинацией приспособления и пользовательского маркера является правильным; в приведенном ниже коде я взял код из вашего вопроса и заполнил пробелы.
Предположим, у нас есть некоторая фиктивная функция для тестирования, которая использует официальный клиент elasticsearch:
# lib.py
from datetime import datetime
from elasticsearch import Elasticsearch
def f():
es = Elasticsearch()
es.indices.create(index='my-index', ignore=400)
return es.index(
index = "my-index",
id=42,
body = {"any": "data", "timestamp": datetime.now()},
)
Добавляем два теста, один не помечен elastic
и должен работать на поддельном клиенте, другой помечен и требует доступа к реальному клиенту:
# test_lib.py
from lib import f
def test_fake():
resp = f()
assert resp["_id"] == "42"
@pytest.mark.elastic
def test_real():
resp = f()
assert resp["_id"] == "42"
Теперь давайте напишем фикстуру elastic()
, которая будет имитировать класс Elasticsearch
в зависимости от того, был ли установлен маркер elastic
:
from unittest.mock import MagicMock, patch
import pytest
@pytest.fixture(autouse=True)
def elastic(request):
should_mock = request.node.get_closest_marker("elastic") is None
if should_mock:
patcher = patch('lib.Elasticsearch')
fake_es = patcher.start()
# this is just a mock example
fake_es.return_value.index.return_value.__getitem__.return_value = "42"
else:
... # e.g. start the real server here etc
yield
if should_mock:
patcher.stop()
Обратите внимание на использование autouse=True
: фикстура будет выполняться при каждом вызове теста, но исправление будет выполняться только в том случае, если тест не помечен. Наличие маркера проверяется с помощью request.node.get_closest_marker("elastic") is None
. Если вы запустите оба теста сейчас, test_fake
пройдет, потому что elastic
имитирует ответ Elasticsearch.index()
, а test_real
завершится ошибкой, если у вас нет сервера, работающего на порту 9200.
Очень доволен этим ответом !!! Я так и не научился правильно использовать макеты и патчи. Я думаю, что это достаточно хороший ответ, чтобы закрепиться, так что принято. Я думаю, меня беспокоит только то, что вы издеваетесь над конкретным случаем возвращения. Я надеялся найти способ заблокировать вызовы, но я думаю, что фиктивная библиотека Elasticsearch, вероятно, подойдет. Благодаря тонну!
Да, поправьте меня, если я ошибаюсь - я полагаю, это скорее заметка для себя, - но использование библиотеки Elasticmock, вероятно, означало бы не использование их декоратора, а вместо этого исправление с использованием _get_elasticmock
. github.com/vrcmarcos/elasticmock/blob/…
@acw вы также можете просто сделать patcher = patch('lib.Elasticsearch', FakeElasticsearch)
; _get_elasticmock
— это просто способ кеширования поддельных клиентов для повторного использования. Я согласен, что публичный API elasticmock
довольно лаконичен, но может предложить больше возможностей для насмешек.
@pytest.mark.usefixtures("elastic")
?