Предварительные тестовые задания с использованием маркеров pytest?

#python #pytest

#питон #пытест

Вопрос:

У меня есть приложение на Python, использующее pytest. Для нескольких моих тестов есть вызовы API для Elasticsearch (с использованием elasticsearch-dsl-py), которые замедляют мои тесты, которые я хотел бы:

  1. предотвращать, если не используется маркер Pytest.
  2. Если используется маркер, я бы хотел, чтобы этот маркер выполнил некоторый код перед запуском теста. Точно так же , как работало бы приспособление , если бы вы им пользовались 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 of elasticmock довольно лаконичен, хотя может предложить больше возможностей для насмешек.