#python #pytest
#питон #пытест
Вопрос:
У меня есть приложение на Python, использующее pytest. Для нескольких моих тестов есть вызовы API для Elasticsearch (с использованием elasticsearch-dsl-py), которые замедляют мои тесты, которые я хотел бы:
- предотвращать, если не используется маркер Pytest.
- Если используется маркер, я бы хотел, чтобы этот маркер выполнил некоторый код перед запуском теста. Точно так же , как работало бы приспособление , если бы вы им пользовались
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.
Комментарии:
1.
@pytest.mark.usefixtures("elastic")
?2. Для издевательства над elasticsearch ознакомьтесь, например
elasticmock
3. Спасибо @hoefling Ах, я думаю
usefixtures
, что декоратор идеально подходит для второго случая, а не для маркеров. Но как мне предотвратить вызовы ES по умолчанию (например, когда маркеры / приспособления не применяются)?4. Думаю, я понимаю, что вы имеете в виду сейчас, добавлю ответ в ближайшее время.
Ответ №1:
Ваш первоначальный подход с использованием комбинации приспособления и пользовательского маркера является правильным; в приведенном ниже коде я взял код из вашего вопроса и заполнил пробелы.
Предположим, у нас есть какая-то фиктивная функция для тестирования, которая использует официальный 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.
Комментарии:
1. Очень доволен этим ответом!!! Я так и не научился правильно использовать mocks и patch. Я думаю, что это достаточно хороший ответ, чтобы закрепиться, поэтому принято. Я думаю, меня беспокоит только то, что вы издевались над конкретным случаем возврата. Я надеялся найти способ блокировать вызовы, но я думаю, что, вероятно, подойдет библиотека макетов Elasticsearch. Огромное спасибо!
2. Да, поправьте меня, если я ошибаюсь — я полагаю, это скорее примечание для себя, — но использование библиотеки Elasticmock, вероятно, означало бы не использовать их декоратор, а вместо этого использовать исправления
_get_elasticmock
. github.com/vrcmarcos/elasticmock/blob /…3. @acw вы также можете просто сделать
patcher = patch('lib.Elasticsearch', FakeElasticsearch)
;_get_elasticmock
это просто способ кэширования поддельных клиентов для повторного использования. Я согласен, что публичный API ofelasticmock
довольно лаконичен, хотя может предложить больше возможностей для насмешек.