简体   繁体   中英

Pre-test tasks using pytest markers?

I've got a Python application using pytest. For several of my tests, there are API calls to Elasticsearch (using elasticsearch-dsl-py) that slow down my tests that I'd like to:

  1. prevent unless a Pytest marker is used.
  2. If a marker is used, I would want that marker to execute some code before the test runs. Just like how a fixture would work if you used yield .

This is mostly inspired by pytest-django where you have to use the django_db marker in order to make a conn to the database (but they throw an error if you try to connect to the DB, whereas I just don't want a call in the first place, that's all).

For example:

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

I think that's a use-case for markers right? Mostly inspired by pytest-django.

Your initial approach with having a combination of a fixture and a custom marker is the correct one; in the code below, I took the code from your question and filled in the gaps.

Suppose we have some dummy function to test that uses the official elasticsearch client:

# 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()},
    )

We add two tests, one is not marked with elastic and should operate on fake client, the other one is marked and needs access to real client:

# 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"

Now let's write the elastic() fixture that will mock the Elasticsearch class depending on whether the elastic marker was set:

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()

Notice the usage of autouse=True : the fixture will be executed on each test invocation, but only do the patching if the test is not marked. This presence of the marker is checked via request.node.get_closest_marker("elastic") is None . If you run both tests now, test_fake will pass because elastic mocks the Elasticsearch.index() response, while test_real will fail, assuming you don't have a server running on port 9200.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM